1
0

Merge branch 'main' into packing

This commit is contained in:
2024-01-10 08:44:08 +01:00
84 changed files with 2742 additions and 1268 deletions

View File

@@ -78,6 +78,17 @@ dotnet_naming_rule.parameters_locals_must_be_camel_case.symbols = parameters_loc
dotnet_naming_rule.parameters_locals_must_be_camel_case.style = camel_case_style dotnet_naming_rule.parameters_locals_must_be_camel_case.style = camel_case_style
dotnet_naming_rule.parameters_locals_must_be_camel_case.severity = warning dotnet_naming_rule.parameters_locals_must_be_camel_case.severity = warning
# Name all private fields starting with underscore
dotnet_naming_rule.private_members_with_underscore.symbols = private_fields
dotnet_naming_rule.private_members_with_underscore.style = prefix_underscore
dotnet_naming_rule.private_members_with_underscore.severity = suggestion
dotnet_naming_symbols.private_fields.applicable_kinds = field
dotnet_naming_symbols.private_fields.applicable_accessibilities = private
dotnet_naming_style.prefix_underscore.capitalization = camel_case
dotnet_naming_style.prefix_underscore.required_prefix = _
[*.cs] [*.cs]
csharp_preferred_modifier_order = public, private, protected, internal, static, extern, new, virtual, abstract, sealed, override, readonly, unsafe, volatile, async:suggestion csharp_preferred_modifier_order = public, private, protected, internal, static, extern, new, virtual, abstract, sealed, override, readonly, unsafe, volatile, async:suggestion

1
.gitignore vendored
View File

@@ -11,7 +11,6 @@
*.userosscache *.userosscache
*.sln.docstates *.sln.docstates
nuget.config
build build
coverage.json coverage.json

View File

@@ -1,115 +1,151 @@
# The image has to use the same version as the .NET UnitTest project # The image should use the same version as the UnitTests are
image: mcr.microsoft.com/dotnet/sdk:6.0 image: mcr.microsoft.com/dotnet/sdk:8.0
variables: variables:
TZ: "Europe/Berlin" TZ: Europe/Berlin
LANG: "de" LANG: de
stages:
- build
stages: - test
- build - deploy
- test
- deploy
build-debug:
stage: build
debug-build: tags:
stage: build - docker
tags: - lnx
- docker rules:
- lnx - if: $CI_COMMIT_TAG == null
except: script:
- tags - dotnet restore --no-cache --force
script: - dotnet build -c Debug --nologo --no-restore --no-incremental
- dotnet restore --no-cache --force - mkdir ./artifacts
- dotnet build -c Debug --nologo --no-restore --no-incremental - mv ./AMWD.Common/bin/Debug/*.nupkg ./artifacts/
- mkdir ./artifacts - mv ./AMWD.Common/bin/Debug/*.snupkg ./artifacts/
- mv ./AMWD.Common/bin/Debug/*.nupkg ./artifacts/ - mv ./AMWD.Common.AspNetCore/bin/Debug/*.nupkg ./artifacts/
- mv ./AMWD.Common/bin/Debug/*.snupkg ./artifacts/ - mv ./AMWD.Common.AspNetCore/bin/Debug/*.snupkg ./artifacts/
- mv ./AMWD.Common.AspNetCore/bin/Debug/*.nupkg ./artifacts/ - mv ./AMWD.Common.EntityFrameworkCore/bin/Debug/*.nupkg ./artifacts/
- mv ./AMWD.Common.AspNetCore/bin/Debug/*.snupkg ./artifacts/ - mv ./AMWD.Common.EntityFrameworkCore/bin/Debug/*.snupkg ./artifacts/
- mv ./AMWD.Common.EntityFrameworkCore/bin/Debug/*.nupkg ./artifacts/ - mv ./AMWD.Common.Test/bin/Debug/*.nupkg ./artifacts/
- mv ./AMWD.Common.EntityFrameworkCore/bin/Debug/*.snupkg ./artifacts/ - mv ./AMWD.Common.Test/bin/Debug/*.snupkg ./artifacts/
- mv ./AMWD.Common.Moq/bin/Debug/*.nupkg ./artifacts/ artifacts:
- mv ./AMWD.Common.Moq/bin/Debug/*.snupkg ./artifacts/ paths:
artifacts: - artifacts/*.nupkg
paths: - artifacts/*.snupkg
- artifacts/*.nupkg expire_in: 7 days
- artifacts/*.snupkg
expire_in: 7 days test-debug:
debug-test:
stage: test stage: test
dependencies: dependencies:
- debug-build - build-debug
tags: tags:
- docker - docker
- lnx - lnx
except: rules:
- tags - if: $CI_COMMIT_TAG == null
# branch-coverage # line-coverage
coverage: '/Total[^|]*\|[^|]*\|\s*([0-9.%]+)/' #coverage: '/Total[^|]*\|\s*([0-9.%]+)/'
script: # branch-coverage
- dotnet restore --no-cache --force coverage: '/Total[^|]*\|[^|]*\|\s*([0-9.%]+)/'
script:
- dotnet restore --no-cache --force
- dotnet test -c Debug --nologo --no-restore - dotnet test -c Debug --nologo --no-restore
build-release:
release-build: stage: build
stage: build tags:
tags: - docker
- docker - lnx
- lnx rules:
- amd64 - if: $CI_COMMIT_TAG != null
only: script:
- tags - dotnet restore --no-cache --force
script: - dotnet build -c Release --nologo --no-restore --no-incremental
- dotnet restore --no-cache --force - mkdir ./artifacts
- dotnet build -c Release --nologo --no-restore --no-incremental - mv ./AMWD.Common/bin/Release/*.nupkg ./artifacts/
- mkdir ./artifacts - mv ./AMWD.Common/bin/Release/*.snupkg ./artifacts/
- mv ./AMWD.Common/bin/Release/*.nupkg ./artifacts/ - mv ./AMWD.Common.AspNetCore/bin/Release/*.nupkg ./artifacts/
- mv ./AMWD.Common/bin/Release/*.snupkg ./artifacts/ - mv ./AMWD.Common.AspNetCore/bin/Release/*.snupkg ./artifacts/
- mv ./AMWD.Common.AspNetCore/bin/Release/*.nupkg ./artifacts/ - mv ./AMWD.Common.EntityFrameworkCore/bin/Release/*.nupkg ./artifacts/
- mv ./AMWD.Common.AspNetCore/bin/Release/*.snupkg ./artifacts/ - mv ./AMWD.Common.EntityFrameworkCore/bin/Release/*.snupkg ./artifacts/
- mv ./AMWD.Common.EntityFrameworkCore/bin/Release/*.nupkg ./artifacts/ - mv ./AMWD.Common.Test/bin/Release/*.nupkg ./artifacts/
- mv ./AMWD.Common.EntityFrameworkCore/bin/Release/*.snupkg ./artifacts/ - mv ./AMWD.Common.Test/bin/Release/*.snupkg ./artifacts/
- mv ./AMWD.Common.Moq/bin/Release/*.nupkg ./artifacts/ artifacts:
- mv ./AMWD.Common.Moq/bin/Release/*.snupkg ./artifacts/ paths:
artifacts: - artifacts/*.nupkg
paths: - artifacts/*.snupkg
- artifacts/*.nupkg expire_in: 1 days
- artifacts/*.snupkg
expire_in: 1 day test-release:
release-test:
stage: test stage: test
dependencies: dependencies:
- release-build - build-release
tags: tags:
- docker - docker
- lnx - lnx
- amd64 rules:
only: - if: $CI_COMMIT_TAG != null
- tags # line-coverage
# line-coverage #coverage: '/Total[^|]*\|\s*([0-9.%]+)/'
coverage: '/Total[^|]*\|\s*([0-9.%]+)/' # branch-coverage
script: coverage: '/Total[^|]*\|[^|]*\|\s*([0-9.%]+)/'
- dotnet restore --no-cache --force script:
- dotnet test -c Release --nologo --no-restore - dotnet restore --no-cache --force
- dotnet test -c Release --nologo --no-restore
release-deploy: deploy-common:
stage: deploy
dependencies:
- build-release
- test-release
tags:
- docker
- lnx
rules:
- if: $CI_COMMIT_TAG =~ /^v[0-9.]+/
script:
- dotnet nuget push -k $BAGET_APIKEY -s https://nuget.am-wd.de/v3/index.json --skip-duplicate artifacts/AMWD.Common.[0-9]*.nupkg
deploy-aspnet:
stage: deploy stage: deploy
dependencies: dependencies:
- release-build - build-release
- release-test - test-release
tags: tags:
- docker - docker
- lnx - lnx
- amd64 rules:
only: - if: $CI_COMMIT_TAG =~ /^asp\/v[0-9.]+/
- tags script:
script: - dotnet nuget push -k $BAGET_APIKEY -s https://nuget.am-wd.de/v3/index.json --skip-duplicate artifacts/AMWD.Common.AspNetCore.*.nupkg
- dotnet nuget push -k $BAGET_APIKEY -s https://nuget.am-wd.de/v3/index.json --skip-duplicate artifacts/*.nupkg
deploy-entityframework:
stage: deploy
dependencies:
- build-release
- test-release
tags:
- docker
- lnx
rules:
- if: $CI_COMMIT_TAG =~ /^efc\/v[0-9.]+/
script:
- dotnet nuget push -k $BAGET_APIKEY -s https://nuget.am-wd.de/v3/index.json --skip-duplicate artifacts/AMWD.Common.EntityFrameworkCore.*.nupkg
deploy-test:
stage: deploy
dependencies:
- build-release
- test-release
tags:
- docker
- lnx
rules:
- if: $CI_COMMIT_TAG =~ /^test\/v[0-9.]+/
script:
- dotnet nuget push -k $BAGET_APIKEY -s https://nuget.am-wd.de/v3/index.json --skip-duplicate artifacts/AMWD.Common.Test.*.nupkg

View File

@@ -1,41 +1,30 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<Configurations>Debug;Release;DebugLocal</Configurations> <TargetFrameworks>net6.0;net8.0</TargetFrameworks>
<TargetFrameworks>netcoreapp3.1;net6.0</TargetFrameworks>
<LangVersion>10.0</LangVersion> <LangVersion>10.0</LangVersion>
<NrtTagMatch>asp/v[0-9]*</NrtTagMatch>
<AssemblyName>AMWD.Common.AspNetCore</AssemblyName> <AssemblyName>AMWD.Common.AspNetCore</AssemblyName>
<RootNamespace>AMWD.Common.AspNetCore</RootNamespace> <RootNamespace>AMWD.Common.AspNetCore</RootNamespace>
<GeneratePackageOnBuild>true</GeneratePackageOnBuild> <GeneratePackageOnBuild>true</GeneratePackageOnBuild>
<PackageId>AMWD.Common.AspNetCore</PackageId> <PackageId>AMWD.Common.AspNetCore</PackageId>
<PackageIcon>icon.png</PackageIcon> <PackageIcon>icon.png</PackageIcon>
<PackageReadmeFile>README.md</PackageReadmeFile>
<Product>AM.WD Common Library for ASP.NET Core</Product> <Product>AM.WD Common Library for ASP.NET Core</Product>
</PropertyGroup> </PropertyGroup>
<PropertyGroup Condition="'$(GITLAB_CI)' == 'true'">
<ContinuousIntegrationBuild>true</ContinuousIntegrationBuild>
</PropertyGroup>
<ItemGroup Condition="'$(GITLAB_CI)' == 'true'">
<SourceLinkGitLabHost Include="git.am-wd.de" Version="$(CI_SERVER_VERSION)" />
<PackageReference Include="Microsoft.SourceLink.GitLab" Version="1.1.1" PrivateAssets="all" />
</ItemGroup>
<ItemGroup> <ItemGroup>
<None Include="../icon.png" Pack="true" PackagePath="/" /> <None Include="../icon.png" Pack="true" PackagePath="/" />
<None Include="../README.md" Pack="true" PackagePath="/" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<FrameworkReference Include="Microsoft.AspNetCore.App" /> <FrameworkReference Include="Microsoft.AspNetCore.App" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.1" /> <PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
<PackageReference Include="Unclassified.DeepConvert" Version="1.4.0" /> <PackageReference Include="Unclassified.DeepConvert" Version="1.4.0" />
<PackageReference Include="Unclassified.NetRevisionTask" Version="0.4.3">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
</ItemGroup> </ItemGroup>
</Project> </Project>

View File

@@ -4,7 +4,7 @@ using System.Net.Http.Headers;
using System.Security.Claims; using System.Security.Claims;
using System.Text; using System.Text;
using System.Threading.Tasks; using System.Threading.Tasks;
using AMWD.Common.AspNetCore.BasicAuthentication; using AMWD.Common.AspNetCore.Security.BasicAuthentication;
using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Filters; using Microsoft.AspNetCore.Mvc.Filters;
@@ -40,18 +40,18 @@ namespace Microsoft.AspNetCore.Authorization
var logger = context.HttpContext.RequestServices.GetService<ILogger<BasicAuthenticationAttribute>>(); var logger = context.HttpContext.RequestServices.GetService<ILogger<BasicAuthenticationAttribute>>();
try try
{ {
var validatorResult = await TrySetHttpUser(context); var validatorResult = await TrySetHttpUser(context).ConfigureAwait(false);
bool isAllowAnonymous = context.ActionDescriptor.EndpointMetadata.OfType<AllowAnonymousAttribute>().Any(); bool isAllowAnonymous = context.ActionDescriptor.EndpointMetadata.OfType<AllowAnonymousAttribute>().Any();
if (isAllowAnonymous) if (isAllowAnonymous)
return; return;
if (!context.HttpContext.Request.Headers.ContainsKey("Authorization")) if (!context.HttpContext.Request.Headers.TryGetValue("Authorization", out var authHeaderValue))
{ {
SetAuthenticateRequest(context); SetAuthenticateRequest(context);
return; return;
} }
var authHeader = AuthenticationHeaderValue.Parse(context.HttpContext.Request.Headers["Authorization"]); var authHeader = AuthenticationHeaderValue.Parse(authHeaderValue);
byte[] decoded = Convert.FromBase64String(authHeader.Parameter); byte[] decoded = Convert.FromBase64String(authHeader.Parameter);
string plain = Encoding.UTF8.GetString(decoded); string plain = Encoding.UTF8.GetString(decoded);
@@ -84,22 +84,22 @@ namespace Microsoft.AspNetCore.Authorization
: validator.Realm : validator.Realm
: Realm; : Realm;
context.HttpContext.Response.Headers["WWW-Authenticate"] = "Basic"; context.HttpContext.Response.Headers.WWWAuthenticate = "Basic";
if (!string.IsNullOrWhiteSpace(realm)) if (!string.IsNullOrWhiteSpace(realm))
context.HttpContext.Response.Headers["WWW-Authenticate"] = $"Basic realm=\"{realm.Trim().Replace("\"", "")}\""; context.HttpContext.Response.Headers.WWWAuthenticate = $"Basic realm=\"{realm.Trim().Replace("\"", "")}\"";
context.HttpContext.Response.StatusCode = StatusCodes.Status401Unauthorized; context.HttpContext.Response.StatusCode = StatusCodes.Status401Unauthorized;
context.Result = new StatusCodeResult(StatusCodes.Status401Unauthorized); context.Result = new StatusCodeResult(StatusCodes.Status401Unauthorized);
} }
private async Task<ClaimsPrincipal> TrySetHttpUser(AuthorizationFilterContext context) private static async Task<ClaimsPrincipal> TrySetHttpUser(AuthorizationFilterContext context)
{ {
var logger = context.HttpContext.RequestServices.GetService<ILogger<BasicAuthenticationAttribute>>(); var logger = context.HttpContext.RequestServices.GetService<ILogger<BasicAuthenticationAttribute>>();
try try
{ {
if (context.HttpContext.Request.Headers.ContainsKey("Authorization")) if (context.HttpContext.Request.Headers.TryGetValue("Authorization", out var authHeaderValue))
{ {
var authHeader = AuthenticationHeaderValue.Parse(context.HttpContext.Request.Headers["Authorization"]); var authHeader = AuthenticationHeaderValue.Parse(authHeaderValue);
byte[] decoded = Convert.FromBase64String(authHeader.Parameter); byte[] decoded = Convert.FromBase64String(authHeader.Parameter);
string plain = Encoding.UTF8.GetString(decoded); string plain = Encoding.UTF8.GetString(decoded);
@@ -111,7 +111,7 @@ namespace Microsoft.AspNetCore.Authorization
if (validator == null) if (validator == null)
return null; return null;
var result = await validator.ValidateAsync(username, password, context.HttpContext.GetRemoteIpAddress(), context.HttpContext.RequestAborted); var result = await validator.ValidateAsync(username, password, context.HttpContext.GetRemoteIpAddress(), context.HttpContext.RequestAborted).ConfigureAwait(false);
if (result == null) if (result == null)
return null; return null;

View File

@@ -50,7 +50,7 @@ namespace Microsoft.AspNetCore.Mvc.Filters
private const string VerificationUrl = "https://www.google.com/recaptcha/api/siteverify"; private const string VerificationUrl = "https://www.google.com/recaptcha/api/siteverify";
private string privateKey; private string _privateKey;
/// <summary> /// <summary>
/// Executes the validattion in background. /// Executes the validattion in background.
@@ -61,13 +61,13 @@ namespace Microsoft.AspNetCore.Mvc.Filters
public override async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next) public override async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next)
{ {
var configuration = context.HttpContext.RequestServices.GetService<IConfiguration>(); var configuration = context.HttpContext.RequestServices.GetService<IConfiguration>();
privateKey = configuration?.GetValue<string>("Google:ReCaptcha:PrivateKey"); _privateKey = configuration?.GetValue<string>("Google:ReCaptcha:PrivateKey");
if (string.IsNullOrWhiteSpace(privateKey)) if (string.IsNullOrWhiteSpace(_privateKey))
return; return;
await DoValidation(context); await DoValidation(context).ConfigureAwait(false);
await base.OnActionExecutionAsync(context, next); await base.OnActionExecutionAsync(context, next).ConfigureAwait(false);
} }
private async Task DoValidation(ActionExecutingContext context) private async Task DoValidation(ActionExecutingContext context)
@@ -82,7 +82,7 @@ namespace Microsoft.AspNetCore.Mvc.Filters
return; return;
} }
await Validate(context, token); await Validate(context, token).ConfigureAwait(false);
} }
private async Task Validate(ActionExecutingContext context, string token) private async Task Validate(ActionExecutingContext context, string token)
@@ -90,11 +90,11 @@ namespace Microsoft.AspNetCore.Mvc.Filters
using var httpClient = new HttpClient(); using var httpClient = new HttpClient();
var param = new Dictionary<string, string> var param = new Dictionary<string, string>
{ {
{ "secret", privateKey }, { "secret", _privateKey },
{ "response", token } { "response", token }
}; };
var response = await httpClient.PostAsync(VerificationUrl, new FormUrlEncodedContent(param)); var response = await httpClient.PostAsync(VerificationUrl, new FormUrlEncodedContent(param)).ConfigureAwait(false);
string json = await response.Content.ReadAsStringAsync(); string json = await response.Content.ReadAsStringAsync().ConfigureAwait(false);
var result = JsonConvert.DeserializeObject<Response>(json); var result = JsonConvert.DeserializeObject<Response>(json);
if (result?.Success != true) if (result?.Success != true)

View File

@@ -1,16 +1,16 @@
using System.Linq; using System.Linq;
using System.Net; using System.Net;
using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.HttpOverrides;
using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using IPNetwork = Microsoft.AspNetCore.HttpOverrides.IPNetwork;
namespace Microsoft.AspNetCore.Mvc.Filters namespace Microsoft.AspNetCore.Mvc.Filters
{ {
/// <summary> /// <summary>
/// Implements an IP filter. Only defined addresses are allowed to access. /// Implements an IP filter. Only defined addresses are allowed to access.
/// </summary> /// </summary>
public class IPWhitelistAttribute : ActionFilterAttribute public class IPAllowListAttribute : ActionFilterAttribute
{ {
/// <summary> /// <summary>
/// Gets or sets a value indicating whether local (localhost) access is granted (Default: true). /// Gets or sets a value indicating whether local (localhost) access is granted (Default: true).

View File

@@ -1,21 +1,21 @@
using System.Linq; using System.Linq;
using System.Net; using System.Net;
using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.HttpOverrides;
using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using IPNetwork = Microsoft.AspNetCore.HttpOverrides.IPNetwork;
namespace Microsoft.AspNetCore.Mvc.Filters namespace Microsoft.AspNetCore.Mvc.Filters
{ {
/// <summary> /// <summary>
/// Implements an IP filter. The defined addresses are blocked. /// Implements an IP filter. The defined addresses are blocked.
/// </summary> /// </summary>
public class IPBlacklistAttribute : ActionFilterAttribute public class IPBlockListAttribute : ActionFilterAttribute
{ {
/// <summary> /// <summary>
/// Gets or sets a value indicating whether local (localhost) access is blocked (Default: false). /// Gets or sets a value indicating whether local (localhost) access is blocked (Default: false).
/// </summary> /// </summary>
public bool RestrictLocalAccess { get; set; } public bool BlockLocalAccess { get; set; }
/// <summary> /// <summary>
/// Gets or sets a configuration key where the blocked IP addresses are defined. /// Gets or sets a configuration key where the blocked IP addresses are defined.
@@ -35,7 +35,7 @@ namespace Microsoft.AspNetCore.Mvc.Filters
/// <summary> /// <summary>
/// Gets or sets a comma separated list of blocked IP addresses. /// Gets or sets a comma separated list of blocked IP addresses.
/// </summary> /// </summary>
public string RestrictedIpAddresses { get; set; } public string BlockedIpAddresses { get; set; }
/// <inheritdoc/> /// <inheritdoc/>
public override void OnActionExecuting(ActionExecutingContext context) public override void OnActionExecuting(ActionExecutingContext context)
@@ -43,13 +43,13 @@ namespace Microsoft.AspNetCore.Mvc.Filters
base.OnActionExecuting(context); base.OnActionExecuting(context);
context.HttpContext.Items["RemoteAddress"] = context.HttpContext.GetRemoteIpAddress(); context.HttpContext.Items["RemoteAddress"] = context.HttpContext.GetRemoteIpAddress();
if (!RestrictLocalAccess && context.HttpContext.IsLocalRequest()) if (!BlockLocalAccess && context.HttpContext.IsLocalRequest())
return; return;
var remoteIpAddress = context.HttpContext.GetRemoteIpAddress(); var remoteIpAddress = context.HttpContext.GetRemoteIpAddress();
if (!string.IsNullOrWhiteSpace(RestrictedIpAddresses)) if (!string.IsNullOrWhiteSpace(BlockedIpAddresses))
{ {
string[] ipAddresses = RestrictedIpAddresses.Split(','); string[] ipAddresses = BlockedIpAddresses.Split(',');
foreach (string ipAddress in ipAddresses) foreach (string ipAddress in ipAddresses)
{ {
if (string.IsNullOrWhiteSpace(ipAddress)) if (string.IsNullOrWhiteSpace(ipAddress))

View File

@@ -1,76 +0,0 @@
using System;
using System.Linq;
using System.Net.Http.Headers;
using System.Security.Claims;
using System.Text;
using System.Text.Encodings.Web;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
namespace AMWD.Common.AspNetCore.BasicAuthentication
{
/// <summary>
/// Implements the <see cref="AuthenticationHandler{TOptions}"/> for Basic Authentication.
/// </summary>
[System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage]
public class BasicAuthenticationHandler : AuthenticationHandler<AuthenticationSchemeOptions>
{
private readonly ILogger logger;
private readonly IBasicAuthenticationValidator validator;
/// <summary>
/// Initializes a new instance of the <see cref="BasicAuthenticationHandler"/> class.
/// </summary>
/// <param name="options">The authentication scheme options.</param>
/// <param name="loggerFactory">The logger factory.</param>
/// <param name="encoder">The URL encoder.</param>
/// <param name="clock">The system clock.</param>
/// <param name="validator">An basic autentication validator implementation.</param>
public BasicAuthenticationHandler(IOptionsMonitor<AuthenticationSchemeOptions> options, ILoggerFactory loggerFactory, UrlEncoder encoder, ISystemClock clock, IBasicAuthenticationValidator validator)
: base(options, loggerFactory, encoder, clock)
{
logger = loggerFactory.CreateLogger<BasicAuthenticationHandler>();
this.validator = validator;
}
/// <inheritdoc/>
protected override async Task<AuthenticateResult> HandleAuthenticateAsync()
{
var endpoint = Context.GetEndpoint();
if (endpoint?.Metadata?.GetMetadata<IAllowAnonymous>() != null)
return AuthenticateResult.NoResult();
if (!Request.Headers.ContainsKey("Authorization"))
return AuthenticateResult.Fail("Authorization header missing");
ClaimsPrincipal principal;
try
{
var authHeader = AuthenticationHeaderValue.Parse(Request.Headers["Authorization"]);
string plain = Encoding.UTF8.GetString(Convert.FromBase64String(authHeader.Parameter));
// See: https://www.rfc-editor.org/rfc/rfc2617, page 6
string username = plain.Split(':').First();
string password = plain[(username.Length + 1)..];
var ipAddress = Context.GetRemoteIpAddress();
principal = await validator.ValidateAsync(username, password, ipAddress, Context.RequestAborted);
}
catch (Exception ex)
{
logger.LogError(ex, $"Handling the Basic Authentication failed: {ex.Message}");
return AuthenticateResult.Fail("Authorization header invalid");
}
if (principal == null)
return AuthenticateResult.Fail("Invalid credentials");
var ticket = new AuthenticationTicket(principal, Scheme.Name);
return AuthenticateResult.Success(ticket);
}
}
}

View File

@@ -2,6 +2,7 @@
using System.Net; using System.Net;
using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.HttpOverrides; using Microsoft.AspNetCore.HttpOverrides;
using IPNetwork = Microsoft.AspNetCore.HttpOverrides.IPNetwork;
namespace Microsoft.AspNetCore.Builder namespace Microsoft.AspNetCore.Builder
{ {
@@ -20,15 +21,19 @@ namespace Microsoft.AspNetCore.Builder
/// <br/> /// <br/>
/// Additionally you can specify the proxy server by using <paramref name="address"/> or a <paramref name="network"/> when there are multiple proxy servers. /// Additionally you can specify the proxy server by using <paramref name="address"/> or a <paramref name="network"/> when there are multiple proxy servers.
/// <br/> /// <br/>
/// When no <paramref name="address"/> oder <paramref name="network"/> is set, the default IPv4 private subnets are configured:<br/> /// When no <paramref name="address"/> oder <paramref name="network"/> is set, the default subnets are configured:<br/>
/// - <c>127.0.0.0/8</c><br/>
/// - <c>10.0.0.0/8</c><br/> /// - <c>10.0.0.0/8</c><br/>
/// - <c>172.16.0.0/12</c><br/> /// - <c>172.16.0.0/12</c><br/>
/// - <c>192.168.0.0/16</c> /// - <c>192.168.0.0/16</c><br/>
///
/// - <c>::1/128</c><br/>
/// - <c>fd00::/8</c>
/// </remarks> /// </remarks>
/// <param name="app">The application builder.</param> /// <param name="app">The application builder.</param>
/// <param name="network">The <see cref="IPNetwork"/> where proxy requests are received from (optional).</param> /// <param name="network">The <see cref="IPNetwork"/> where proxy requests are received from (optional).</param>
/// <param name="address">The <see cref="IPAddress"/> where proxy requests are received from (optional).</param> /// <param name="address">The <see cref="IPAddress"/> where proxy requests are received from (optional).</param>
public static void UseProxyHosting(this IApplicationBuilder app, IPNetwork network = null, IPAddress address = null) public static IApplicationBuilder UseProxyHosting(this IApplicationBuilder app, IPNetwork network = null, IPAddress address = null)
{ {
string path = Environment.GetEnvironmentVariable("ASPNETCORE_APPL_PATH"); string path = Environment.GetEnvironmentVariable("ASPNETCORE_APPL_PATH");
if (!string.IsNullOrWhiteSpace(path)) if (!string.IsNullOrWhiteSpace(path))
@@ -40,9 +45,17 @@ namespace Microsoft.AspNetCore.Builder
if (network == null && address == null) if (network == null && address == null)
{ {
// localhost
options.KnownNetworks.Add(new IPNetwork(IPAddress.Parse("127.0.0.0"), 8));
options.KnownNetworks.Add(new IPNetwork(IPAddress.Parse("::1"), 128));
// private IPv4 networks
options.KnownNetworks.Add(new IPNetwork(IPAddress.Parse("10.0.0.0"), 8)); options.KnownNetworks.Add(new IPNetwork(IPAddress.Parse("10.0.0.0"), 8));
options.KnownNetworks.Add(new IPNetwork(IPAddress.Parse("172.16.0.0"), 12)); options.KnownNetworks.Add(new IPNetwork(IPAddress.Parse("172.16.0.0"), 12));
options.KnownNetworks.Add(new IPNetwork(IPAddress.Parse("192.168.0.0"), 16)); options.KnownNetworks.Add(new IPNetwork(IPAddress.Parse("192.168.0.0"), 16));
// private IPv6 networks
options.KnownNetworks.Add(new IPNetwork(IPAddress.Parse("fd00::"), 8));
} }
if (network != null) if (network != null)
@@ -52,6 +65,8 @@ namespace Microsoft.AspNetCore.Builder
options.KnownProxies.Add(address); options.KnownProxies.Add(address);
app.UseForwardedHeaders(options); app.UseForwardedHeaders(options);
return app;
} }
} }
} }

View File

@@ -1,4 +1,6 @@
using System.Net; using System;
using System.Linq;
using System.Net;
using Microsoft.AspNetCore.Antiforgery; using Microsoft.AspNetCore.Antiforgery;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
@@ -9,43 +11,90 @@ namespace Microsoft.AspNetCore.Http
/// </summary> /// </summary>
public static class HttpContextExtensions public static class HttpContextExtensions
{ {
// Search these additional headers for a remote client ip address.
private static readonly string[] _defaultIpHeaderNames = new[]
{
"Cf-Connecting-Ip", // set by Cloudflare
"X-Real-IP", // wide-spread alternative to X-Forwarded-For
"X-Forwarded-For", // commonly used on all known proxies
};
/// <summary> /// <summary>
/// Retrieves the antiforgery token. /// Retrieves the antiforgery token.
/// </summary> /// </summary>
/// <param name="httpContext">The current <see cref="HttpContext"/>.</param> /// <param name="httpContext">The current <see cref="HttpContext"/>.</param>
/// <returns>Name and value of the token.</returns> /// <returns>FormName, HeaderName and Value of the antiforgery token.</returns>
public static (string Name, string Value) GetAntiforgeryToken(this HttpContext httpContext) public static (string FormName, string HeaderName, string Value) GetAntiforgeryToken(this HttpContext httpContext)
{ {
var af = httpContext.RequestServices.GetService<IAntiforgery>(); var antiforgery = httpContext.RequestServices.GetService<IAntiforgery>();
var set = af?.GetAndStoreTokens(httpContext); var tokenSet = antiforgery?.GetAndStoreTokens(httpContext);
return (Name: set?.FormFieldName, Value: set?.RequestToken); return (tokenSet?.FormFieldName, tokenSet?.HeaderName, tokenSet?.RequestToken);
} }
/// <summary> /// <summary>
/// Returns the remote ip address. /// Returns the remote ip address.
/// </summary> /// </summary>
/// <remarks>
/// Searches for additional headers in the following order:
/// <list type="number">
/// <item>Cf-Connecting-Ip</item>
/// <item>X-Real-IP</item>
/// <item>X-Forwarded-For</item>
/// </list>
/// </remarks>
/// <param name="httpContext">The current <see cref="HttpContext"/>.</param> /// <param name="httpContext">The current <see cref="HttpContext"/>.</param>
/// <param name="headerName">The name of the header to resolve the <see cref="IPAddress"/> when behind a proxy (Default: X-Forwarded-For).</param> /// <param name="ipHeaderName">The name of the header to resolve the <see cref="IPAddress"/> when behind a proxy.</param>
/// <returns>The ip address of the client.</returns> /// <returns>The ip address of the client.</returns>
public static IPAddress GetRemoteIpAddress(this HttpContext httpContext, string headerName = "X-Forwarded-For") public static IPAddress GetRemoteIpAddress(this HttpContext httpContext, string ipHeaderName = null)
{ {
string forwardedHeader = httpContext.Request.Headers[headerName].ToString(); string forwardedForAddress = null;
if (!string.IsNullOrWhiteSpace(forwardedHeader) && IPAddress.TryParse(forwardedHeader, out var forwarded))
return forwarded;
return httpContext.Connection.RemoteIpAddress; var headerNames = string.IsNullOrWhiteSpace(ipHeaderName)
? _defaultIpHeaderNames
: new[] { ipHeaderName }.Concat(_defaultIpHeaderNames);
foreach (string headerName in headerNames)
{
if (!httpContext.Request.Headers.ContainsKey(headerName))
continue;
// X-Forwarded-For can contain multiple comma-separated addresses.
forwardedForAddress = httpContext.Request.Headers[headerName].ToString()
.Split(',', StringSplitOptions.TrimEntries)
.First();
break;
}
if (!string.IsNullOrWhiteSpace(forwardedForAddress) && IPAddress.TryParse(forwardedForAddress, out var remoteAddress))
{
return remoteAddress.IsIPv4MappedToIPv6
? remoteAddress.MapToIPv4()
: remoteAddress;
}
return httpContext.Connection.RemoteIpAddress.IsIPv4MappedToIPv6
? httpContext.Connection.RemoteIpAddress.MapToIPv4()
: httpContext.Connection.RemoteIpAddress;
} }
/// <summary> /// <summary>
/// Returns whether the request was made locally. /// Returns whether the request was made locally.
/// </summary> /// </summary>
/// <remarks>
/// Searches for additional headers in the following order:
/// <list type="number">
/// <item>Cf-Connecting-Ip</item>
/// <item>X-Real-IP</item>
/// <item>X-Forwarded-For</item>
/// </list>
/// </remarks>
/// <param name="httpContext">The current <see cref="HttpContext"/>.</param> /// <param name="httpContext">The current <see cref="HttpContext"/>.</param>
/// <param name="headerName">The name of the header to resolve the <see cref="IPAddress"/> when behind a proxy (Default: X-Forwarded-For).</param> /// <param name="ipHeaderName">The name of the header to resolve the <see cref="IPAddress"/> when behind a proxy.</param>
/// <returns></returns> /// <returns></returns>
public static bool IsLocalRequest(this HttpContext httpContext, string headerName = "X-Forwarded-For") public static bool IsLocalRequest(this HttpContext httpContext, string ipHeaderName = null)
{ {
var remoteIpAddress = httpContext.GetRemoteIpAddress(headerName); var remoteIpAddress = httpContext.GetRemoteIpAddress(ipHeaderName);
return httpContext.Connection.LocalIpAddress.Equals(remoteIpAddress); return httpContext.Connection.LocalIpAddress.Equals(remoteIpAddress);
} }

View File

@@ -15,11 +15,11 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
/// <typeparam name="TModel">The type of the model.</typeparam> /// <typeparam name="TModel">The type of the model.</typeparam>
/// <typeparam name="TProperty">The type of the property.</typeparam> /// <typeparam name="TProperty">The type of the property.</typeparam>
/// <param name="modelState">The <see cref="ModelStateDictionary"/> instance.</param> /// <param name="modelState">The <see cref="ModelStateDictionary"/> instance.</param>
/// <param name="model">The model. Only used to infer the model type.</param> /// <param name="_">The model. Only used to infer the model type.</param>
/// <param name="keyExpression">The <see cref="MemberExpression"/> that specifies the property.</param> /// <param name="keyExpression">The <see cref="MemberExpression"/> that specifies the property.</param>
/// <param name="errorMessage">The error message to add.</param> /// <param name="errorMessage">The error message to add.</param>
/// <exception cref="InvalidOperationException">No member expression provided.</exception> /// <exception cref="InvalidOperationException">No member expression provided.</exception>
public static void AddModelError<TModel, TProperty>(this ModelStateDictionary modelState, TModel model, Expression<Func<TModel, TProperty>> keyExpression, string errorMessage) public static void AddModelError<TModel, TProperty>(this ModelStateDictionary modelState, TModel _, Expression<Func<TModel, TProperty>> keyExpression, string errorMessage)
{ {
if (modelState is null) if (modelState is null)
throw new ArgumentNullException(nameof(modelState)); throw new ArgumentNullException(nameof(modelState));

View File

@@ -18,7 +18,8 @@ namespace Microsoft.Extensions.DependencyInjection
where TService : class, IHostedService where TService : class, IHostedService
{ {
services.AddSingleton<TService>(); services.AddSingleton<TService>();
services.AddSingleton<IHostedService, BackgroundServiceStarter<TService>>(); services.AddHostedService(serviceProvider => serviceProvider.GetRequiredService<TService>());
return services; return services;
} }
@@ -30,10 +31,11 @@ namespace Microsoft.Extensions.DependencyInjection
/// <param name="services">The <see cref="IServiceCollection"/> to add the service to.</param> /// <param name="services">The <see cref="IServiceCollection"/> to add the service to.</param>
/// <returns>A reference to this instance after the operation has completed.</returns> /// <returns>A reference to this instance after the operation has completed.</returns>
public static IServiceCollection AddSingletonHostedService<TService, TImplementation>(this IServiceCollection services) public static IServiceCollection AddSingletonHostedService<TService, TImplementation>(this IServiceCollection services)
where TService : class, IHostedService where TImplementation : class, TService where TService : class, IHostedService
where TImplementation : class, TService
{ {
services.AddSingleton<TService, TImplementation>(); services.AddSingleton<TService, TImplementation>();
services.AddSingleton<IHostedService, BackgroundServiceStarter<TService>>(); services.AddHostedService(serviceProvider => serviceProvider.GetRequiredService<TService>());
return services; return services;
} }

View File

@@ -12,9 +12,9 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
[System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage]
public class InvariantFloatingPointModelBinder : IModelBinder public class InvariantFloatingPointModelBinder : IModelBinder
{ {
private readonly NumberStyles supportedNumberStyles; private readonly NumberStyles _supportedNumberStyles;
private readonly ILogger logger; private readonly ILogger _logger;
private readonly CultureInfo cultureInfo; private readonly CultureInfo _cultureInfo;
/// <summary> /// <summary>
/// Initializes a new instance of <see cref="InvariantFloatingPointModelBinder"/>. /// Initializes a new instance of <see cref="InvariantFloatingPointModelBinder"/>.
@@ -24,10 +24,10 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
/// <param name="loggerFactory">The <see cref="ILoggerFactory"/>.</param> /// <param name="loggerFactory">The <see cref="ILoggerFactory"/>.</param>
public InvariantFloatingPointModelBinder(NumberStyles supportedStyles, CultureInfo cultureInfo, ILoggerFactory loggerFactory) public InvariantFloatingPointModelBinder(NumberStyles supportedStyles, CultureInfo cultureInfo, ILoggerFactory loggerFactory)
{ {
this.cultureInfo = cultureInfo ?? throw new ArgumentNullException(nameof(cultureInfo)); _cultureInfo = cultureInfo ?? throw new ArgumentNullException(nameof(cultureInfo));
supportedNumberStyles = supportedStyles; _supportedNumberStyles = supportedStyles;
logger = loggerFactory?.CreateLogger<InvariantFloatingPointModelBinder>(); _logger = loggerFactory?.CreateLogger<InvariantFloatingPointModelBinder>();
} }
/// <inheritdoc /> /// <inheritdoc />
@@ -36,15 +36,15 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
if (bindingContext == null) if (bindingContext == null)
throw new ArgumentNullException(nameof(bindingContext)); throw new ArgumentNullException(nameof(bindingContext));
logger?.AttemptingToBindModel(bindingContext); _logger?.AttemptingToBindModel(bindingContext);
string modelName = bindingContext.ModelName; string modelName = bindingContext.ModelName;
var valueProviderResult = bindingContext.ValueProvider.GetValue(modelName); var valueProviderResult = bindingContext.ValueProvider.GetValue(modelName);
if (valueProviderResult == ValueProviderResult.None) if (valueProviderResult == ValueProviderResult.None)
{ {
logger?.FoundNoValueInRequest(bindingContext); _logger?.FoundNoValueInRequest(bindingContext);
// no entry // no entry
logger?.DoneAttemptingToBindModel(bindingContext); _logger?.DoneAttemptingToBindModel(bindingContext);
return Task.CompletedTask; return Task.CompletedTask;
} }
@@ -56,7 +56,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
try try
{ {
string value = valueProviderResult.FirstValue; string value = valueProviderResult.FirstValue;
var culture = cultureInfo ?? valueProviderResult.Culture; var culture = _cultureInfo ?? valueProviderResult.Culture;
object model; object model;
if (string.IsNullOrWhiteSpace(value)) if (string.IsNullOrWhiteSpace(value))
@@ -66,15 +66,15 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
} }
else if (type == typeof(float)) else if (type == typeof(float))
{ {
model = float.Parse(value, supportedNumberStyles, culture); model = float.Parse(value, _supportedNumberStyles, culture);
} }
else if (type == typeof(double)) else if (type == typeof(double))
{ {
model = double.Parse(value, supportedNumberStyles, culture); model = double.Parse(value, _supportedNumberStyles, culture);
} }
else if (type == typeof(decimal)) else if (type == typeof(decimal))
{ {
model = decimal.Parse(value, supportedNumberStyles, culture); model = decimal.Parse(value, _supportedNumberStyles, culture);
} }
else else
{ {
@@ -113,7 +113,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
// Conversion failed. // Conversion failed.
} }
logger?.DoneAttemptingToBindModel(bindingContext); _logger?.DoneAttemptingToBindModel(bindingContext);
return Task.CompletedTask; return Task.CompletedTask;
} }
} }

View File

@@ -0,0 +1,96 @@
using System;
using System.Linq;
using System.Net.Http.Headers;
using System.Security.Claims;
using System.Text;
using System.Text.Encodings.Web;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
namespace AMWD.Common.AspNetCore.Security.BasicAuthentication
{
/// <summary>
/// Implements the <see cref="AuthenticationHandler{TOptions}"/> for Basic Authentication.
/// </summary>
[System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage]
public class BasicAuthenticationHandler : AuthenticationHandler<AuthenticationSchemeOptions>
{
private readonly ILogger _logger;
private readonly IBasicAuthenticationValidator _validator;
#if NET8_0_OR_GREATER
/// <summary>
/// Initializes a new instance of the <see cref="BasicAuthenticationHandler"/> class.
/// </summary>
/// <param name="options">The monitor for the options instance.</param>
/// <param name="logger">The <see cref="ILoggerFactory"/>.</param>
/// <param name="encoder">The <see cref="UrlEncoder"/>.</param>
/// <param name="validator">An basic autentication validator implementation.</param>
public BasicAuthenticationHandler(IOptionsMonitor<AuthenticationSchemeOptions> options, ILoggerFactory logger, UrlEncoder encoder, IBasicAuthenticationValidator validator)
: base(options, logger, encoder)
{
_logger = logger.CreateLogger<BasicAuthenticationHandler>();
_validator = validator;
}
#endif
#if NET6_0
/// <summary>
/// Initializes a new instance of the <see cref="BasicAuthenticationHandler"/> class.
/// </summary>
/// <param name="options" > The monitor for the options instance.</param>
/// <param name="logger">The <see cref="ILoggerFactory"/>.</param>
/// <param name="encoder">The <see cref="UrlEncoder"/>.</param>
/// <param name="clock">The <see cref="ISystemClock"/>.</param>
/// <param name="validator">An basic autentication validator implementation.</param>
public BasicAuthenticationHandler(IOptionsMonitor<AuthenticationSchemeOptions> options, ILoggerFactory logger, UrlEncoder encoder, ISystemClock clock, IBasicAuthenticationValidator validator)
: base(options, logger, encoder, clock)
{
_logger = logger.CreateLogger<BasicAuthenticationHandler>();
_validator = validator;
}
#endif
/// <inheritdoc/>
protected override async Task<AuthenticateResult> HandleAuthenticateAsync()
{
var endpoint = Context.GetEndpoint();
if (endpoint?.Metadata?.GetMetadata<IAllowAnonymous>() != null)
return AuthenticateResult.NoResult();
if (!Request.Headers.TryGetValue("Authorization", out var authHeaderValue))
return AuthenticateResult.Fail("Authorization header missing");
ClaimsPrincipal principal;
try
{
var authHeader = AuthenticationHeaderValue.Parse(authHeaderValue);
string plain = Encoding.UTF8.GetString(Convert.FromBase64String(authHeader.Parameter));
// See: https://www.rfc-editor.org/rfc/rfc2617, page 6
string username = plain.Split(':').First();
string password = plain[(username.Length + 1)..];
var ipAddress = Context.GetRemoteIpAddress();
principal = await _validator.ValidateAsync(username, password, ipAddress, Context.RequestAborted).ConfigureAwait(false);
}
catch (Exception ex)
{
_logger.LogError(ex, $"Handling the Basic Authentication failed: {ex.Message}");
return AuthenticateResult.Fail("Authorization header invalid");
}
if (principal == null)
return AuthenticateResult.Fail("Invalid credentials");
var ticket = new AuthenticationTicket(principal, Scheme.Name);
return AuthenticateResult.Success(ticket);
}
}
}

View File

@@ -6,15 +6,15 @@ using System.Threading.Tasks;
using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
namespace AMWD.Common.AspNetCore.BasicAuthentication namespace AMWD.Common.AspNetCore.Security.BasicAuthentication
{ {
/// <summary> /// <summary>
/// Implements a basic authentication. /// Implements a basic authentication.
/// </summary> /// </summary>
public class BasicAuthenticationMiddleware public class BasicAuthenticationMiddleware
{ {
private readonly RequestDelegate next; private readonly RequestDelegate _next;
private readonly IBasicAuthenticationValidator validator; private readonly IBasicAuthenticationValidator _validator;
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="BasicAuthenticationMiddleware"/> class. /// Initializes a new instance of the <see cref="BasicAuthenticationMiddleware"/> class.
@@ -23,8 +23,8 @@ namespace AMWD.Common.AspNetCore.BasicAuthentication
/// <param name="validator">A basic authentication validator.</param> /// <param name="validator">A basic authentication validator.</param>
public BasicAuthenticationMiddleware(RequestDelegate next, IBasicAuthenticationValidator validator) public BasicAuthenticationMiddleware(RequestDelegate next, IBasicAuthenticationValidator validator)
{ {
this.next = next; _next = next;
this.validator = validator; _validator = validator;
} }
/// <summary> /// <summary>
@@ -37,7 +37,7 @@ namespace AMWD.Common.AspNetCore.BasicAuthentication
{ {
if (!httpContext.Request.Headers.ContainsKey("Authorization")) if (!httpContext.Request.Headers.ContainsKey("Authorization"))
{ {
SetAuthenticateRequest(httpContext, validator.Realm); SetAuthenticateRequest(httpContext, _validator.Realm);
return; return;
} }
@@ -51,14 +51,14 @@ namespace AMWD.Common.AspNetCore.BasicAuthentication
string username = plain.Split(':').First(); string username = plain.Split(':').First();
string password = plain[(username.Length + 1)..]; string password = plain[(username.Length + 1)..];
var principal = await validator.ValidateAsync(username, password, httpContext.GetRemoteIpAddress(), httpContext.RequestAborted); var principal = await _validator.ValidateAsync(username, password, httpContext.GetRemoteIpAddress(), httpContext.RequestAborted).ConfigureAwait(false);
if (principal == null) if (principal == null)
{ {
SetAuthenticateRequest(httpContext, validator.Realm); SetAuthenticateRequest(httpContext, _validator.Realm);
return; return;
} }
await next.Invoke(httpContext); await _next.Invoke(httpContext).ConfigureAwait(false);
} }
catch (Exception ex) catch (Exception ex)
{ {

View File

@@ -3,7 +3,7 @@ using System.Security.Claims;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
namespace AMWD.Common.AspNetCore.BasicAuthentication namespace AMWD.Common.AspNetCore.Security.BasicAuthentication
{ {
/// <summary> /// <summary>
/// Interface representing the validation of a basic authentication. /// Interface representing the validation of a basic authentication.

View File

@@ -0,0 +1,19 @@
using Microsoft.AspNetCore.Builder;
namespace AMWD.Common.AspNetCore.Security.PathProtection
{
/// <summary>
/// Extension for <see cref="IApplicationBuilder"/> to enable folder protection.
/// </summary>
[System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage]
public static class ProtectedPathExtensions
{
/// <summary>
/// Provide protected paths even for static files.
/// </summary>
/// <param name="app">The <see cref="IApplicationBuilder"/>.</param>
/// <param name="options">The <see cref="ProtectedPathOptions"/> with path and policy name.</param>
public static IApplicationBuilder UseProtectedPath(this IApplicationBuilder app, ProtectedPathOptions options)
=> app.UseMiddleware<ProtectedPathMiddleware>(options);
}
}

View File

@@ -0,0 +1,50 @@
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
namespace AMWD.Common.AspNetCore.Security.PathProtection
{
/// <summary>
/// Implements a check to provide protected paths.
/// </summary>
public class ProtectedPathMiddleware
{
private readonly RequestDelegate _next;
private readonly PathString _path;
private readonly string _policyName;
/// <summary>
/// Initializes a new instance of the <see cref="ProtectedPathExtensions"/> class.
/// </summary>
/// <param name="next">The following delegate in the process chain.</param>
/// <param name="options">The options to configure the middleware.</param>
public ProtectedPathMiddleware(RequestDelegate next, ProtectedPathOptions options)
{
_next = next;
_path = options.Path;
_policyName = options.PolicyName;
}
/// <summary>
/// The delegate invokation.
/// Performs the protection check.
/// </summary>
/// <param name="httpContext">The corresponding HTTP context.</param>
/// <param name="authorizationService">The <see cref="IAuthorizationService"/>.</param>
/// <returns>An awaitable task.</returns>
public async Task InvokeAsync(HttpContext httpContext, IAuthorizationService authorizationService)
{
if (httpContext.Request.Path.StartsWithSegments(_path))
{
var result = await authorizationService.AuthorizeAsync(httpContext.User, null, _policyName).ConfigureAwait(false);
if (!result.Succeeded)
{
await httpContext.ChallengeAsync().ConfigureAwait(false);
return;
}
}
await _next.Invoke(httpContext).ConfigureAwait(false);
}
}
}

View File

@@ -0,0 +1,20 @@
using Microsoft.AspNetCore.Http;
namespace AMWD.Common.AspNetCore.Security.PathProtection
{
/// <summary>
/// Options to define which folder should be protected.
/// </summary>
public class ProtectedPathOptions
{
/// <summary>
/// Gets or sets the path to the protected folder.
/// </summary>
public PathString Path { get; set; }
/// <summary>
/// Gets or sets the policy name to use.
/// </summary>
public string PolicyName { get; set; }
}
}

View File

@@ -21,7 +21,7 @@ namespace Microsoft.AspNetCore.Razor.TagHelpers
[HtmlAttributeName("class")] [HtmlAttributeName("class")]
public string CssClass { get; set; } public string CssClass { get; set; }
private IDictionary<string, bool> classValues; private IDictionary<string, bool> _classValues;
/// <summary> /// <summary>
/// Gets or sets a dictionary containing all conditional class names and a boolean condition /// Gets or sets a dictionary containing all conditional class names and a boolean condition
@@ -32,11 +32,11 @@ namespace Microsoft.AspNetCore.Razor.TagHelpers
{ {
get get
{ {
return classValues ??= new Dictionary<string, bool>(StringComparer.OrdinalIgnoreCase); return _classValues ??= new Dictionary<string, bool>(StringComparer.OrdinalIgnoreCase);
} }
set set
{ {
classValues = value; _classValues = value;
} }
} }
@@ -48,7 +48,7 @@ namespace Microsoft.AspNetCore.Razor.TagHelpers
/// <param name="output">A stateful HTML element used to generate an HTML tag.</param> /// <param name="output">A stateful HTML element used to generate an HTML tag.</param>
public override void Process(TagHelperContext context, TagHelperOutput output) public override void Process(TagHelperContext context, TagHelperOutput output)
{ {
var items = classValues.Where(e => e.Value).Select(e => e.Key).ToList(); var items = _classValues.Where(e => e.Value).Select(e => e.Key).ToList();
if (!string.IsNullOrEmpty(CssClass)) if (!string.IsNullOrEmpty(CssClass))
items.Insert(0, CssClass); items.Insert(0, CssClass);

View File

@@ -18,8 +18,8 @@ namespace Microsoft.AspNetCore.Razor.TagHelpers
[HtmlTargetElement("script")] [HtmlTargetElement("script")]
public class IntegrityHashTagHelper : TagHelper public class IntegrityHashTagHelper : TagHelper
{ {
private readonly IWebHostEnvironment env; private readonly IWebHostEnvironment _env;
private readonly string hostUrl; private readonly string _hostUrl;
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="IntegrityHashTagHelper"/> class. /// Initializes a new instance of the <see cref="IntegrityHashTagHelper"/> class.
@@ -28,8 +28,8 @@ namespace Microsoft.AspNetCore.Razor.TagHelpers
/// <param name="configuration">The application configuration.</param> /// <param name="configuration">The application configuration.</param>
public IntegrityHashTagHelper(IWebHostEnvironment env, IConfiguration configuration) public IntegrityHashTagHelper(IWebHostEnvironment env, IConfiguration configuration)
{ {
this.env = env; _env = env;
hostUrl = configuration.GetValue("ASPNETCORE_APPL_URL", "http://localhost/"); _hostUrl = configuration.GetValue("ASPNETCORE_APPL_URL", "http://localhost/");
} }
/// <summary> /// <summary>
@@ -84,11 +84,11 @@ namespace Microsoft.AspNetCore.Razor.TagHelpers
{ {
using var client = new HttpClient(); using var client = new HttpClient();
if (!string.IsNullOrWhiteSpace(hostUrl)) if (!string.IsNullOrWhiteSpace(_hostUrl))
client.DefaultRequestHeaders.Referrer = new Uri(hostUrl); client.DefaultRequestHeaders.Referrer = new Uri(_hostUrl);
var response = await client.GetAsync(source); var response = await client.GetAsync(source).ConfigureAwait(false);
fileBytes = await response.Content.ReadAsByteArrayAsync(); fileBytes = await response.Content.ReadAsByteArrayAsync().ConfigureAwait(false);
} }
catch catch
{ {
@@ -103,13 +103,13 @@ namespace Microsoft.AspNetCore.Razor.TagHelpers
if (source.StartsWith("/")) if (source.StartsWith("/"))
source = source[1..]; source = source[1..];
if (source.Contains("?")) if (source.Contains('?'))
source = source[..source.IndexOf("?")]; source = source[..source.IndexOf("?")];
try try
{ {
string path = Path.Combine(env.WebRootPath, source); string path = Path.Combine(_env.WebRootPath, source);
fileBytes = await File.ReadAllBytesAsync(path); fileBytes = await File.ReadAllBytesAsync(path).ConfigureAwait(false);
} }
catch catch
{ {
@@ -118,7 +118,7 @@ namespace Microsoft.AspNetCore.Razor.TagHelpers
} }
string type; string type;
byte[] hashBytes = new byte[0]; byte[] hashBytes = Array.Empty<byte>();
switch (IntegrityStrength) switch (IntegrityStrength)
{ {
case 512: case 512:

View File

@@ -1,45 +0,0 @@
using System.Threading;
using System.Threading.Tasks;
namespace Microsoft.Extensions.Hosting
{
/// <summary>
/// Wrapper class to start a background service.
/// </summary>
/// <typeparam name="TService">The service type.</typeparam>
[System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage]
public class BackgroundServiceStarter<TService> : IHostedService
where TService : class, IHostedService
{
private readonly TService service;
/// <summary>
/// Initializes an new instance of the <see cref="BackgroundServiceStarter{TService}"/> class.
/// </summary>
/// <param name="backgroundService">The service to work in background.</param>
public BackgroundServiceStarter(TService backgroundService)
{
service = backgroundService;
}
/// <summary>
/// Starts the service.
/// </summary>
/// <param name="cancellationToken"></param>
/// <returns></returns>
public Task StartAsync(CancellationToken cancellationToken)
{
return service.StartAsync(cancellationToken);
}
/// <summary>
/// Stops the service.
/// </summary>
/// <param name="cancellationToken"></param>
/// <returns></returns>
public Task StopAsync(CancellationToken cancellationToken)
{
return service.StopAsync(cancellationToken);
}
}
}

View File

@@ -1,51 +1,38 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<Configurations>Debug;Release;DebugLocal</Configurations> <TargetFrameworks>net6.0;net8.0</TargetFrameworks>
<TargetFrameworks>netcoreapp3.1;net6.0</TargetFrameworks>
<LangVersion>10.0</LangVersion> <LangVersion>10.0</LangVersion>
<NrtTagMatch>efc/v[0-9]*</NrtTagMatch>
<AssemblyName>AMWD.Common.EntityFrameworkCore</AssemblyName> <AssemblyName>AMWD.Common.EntityFrameworkCore</AssemblyName>
<RootNamespace>AMWD.Common.EntityFrameworkCore</RootNamespace> <RootNamespace>AMWD.Common.EntityFrameworkCore</RootNamespace>
<GeneratePackageOnBuild>true</GeneratePackageOnBuild> <GeneratePackageOnBuild>true</GeneratePackageOnBuild>
<PackageId>AMWD.Common.EntityFrameworkCore</PackageId> <PackageId>AMWD.Common.EntityFrameworkCore</PackageId>
<PackageIcon>icon.png</PackageIcon> <PackageIcon>icon.png</PackageIcon>
<PackageReadmeFile>README.md</PackageReadmeFile>
<Product>AM.WD Common Library for EntityFramework Core</Product> <Product>AM.WD Common Library for EntityFramework Core</Product>
</PropertyGroup> </PropertyGroup>
<PropertyGroup Condition="'$(GITLAB_CI)' == 'true'">
<ContinuousIntegrationBuild>true</ContinuousIntegrationBuild>
</PropertyGroup>
<ItemGroup Condition="'$(GITLAB_CI)' == 'true'">
<SourceLinkGitLabHost Include="git.am-wd.de" Version="$(CI_SERVER_VERSION)" />
<PackageReference Include="Microsoft.SourceLink.GitLab" Version="1.1.1" PrivateAssets="all" />
</ItemGroup>
<ItemGroup> <ItemGroup>
<None Include="../icon.png" Pack="true" PackagePath="/" /> <None Include="../icon.png" Pack="true" PackagePath="/" />
</ItemGroup> <None Include="../README.md" Pack="true" PackagePath="/" />
<ItemGroup Condition="'$(TargetFramework)' == 'netcoreapp3.1'">
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="3.1.29" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Relational" Version="3.1.29" />
<PackageReference Include="Microsoft.Extensions.Configuration" Version="3.1.29" />
</ItemGroup> </ItemGroup>
<ItemGroup Condition="'$(TargetFramework)' == 'net6.0'"> <ItemGroup Condition="'$(TargetFramework)' == 'net6.0'">
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="6.0.9" /> <PackageReference Include="Microsoft.EntityFrameworkCore" Version="6.0.25" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Relational" Version="6.0.9" /> <PackageReference Include="Microsoft.EntityFrameworkCore.Relational" Version="6.0.25" />
<PackageReference Include="Microsoft.Extensions.Configuration" Version="6.0.1" /> <PackageReference Include="Microsoft.Extensions.Configuration" Version="6.0.1" />
<PackageReference Include="Microsoft.Extensions.Configuration.Binder" Version="6.0.0" /> <PackageReference Include="Microsoft.Extensions.Configuration.Binder" Version="6.0.0" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup Condition="'$(TargetFramework)' == 'net8.0'">
<PackageReference Include="Unclassified.NetRevisionTask" Version="0.4.3"> <PackageReference Include="Microsoft.EntityFrameworkCore" Version="8.0.0" />
<PrivateAssets>all</PrivateAssets> <PackageReference Include="Microsoft.EntityFrameworkCore.Relational" Version="8.0.0" />
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> <PackageReference Include="Microsoft.Extensions.Configuration" Version="8.0.0" />
</PackageReference> <PackageReference Include="Microsoft.Extensions.Configuration.Binder" Version="8.0.0" />
</ItemGroup> </ItemGroup>
</Project> </Project>

View File

@@ -1,6 +1,4 @@
#if NET6_0_OR_GREATER using System;
using System;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion; using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
namespace AMWD.Common.EntityFrameworkCore.Converters namespace AMWD.Common.EntityFrameworkCore.Converters
@@ -24,5 +22,3 @@ namespace AMWD.Common.EntityFrameworkCore.Converters
{ } { }
} }
} }
#endif

View File

@@ -1,6 +1,4 @@
#if NET6_0_OR_GREATER using System;
using System;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion; using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
namespace AMWD.Common.EntityFrameworkCore.Converters namespace AMWD.Common.EntityFrameworkCore.Converters
@@ -24,5 +22,3 @@ namespace AMWD.Common.EntityFrameworkCore.Converters
{ } { }
} }
} }
#endif

View File

@@ -1,6 +1,4 @@
#if NET6_0_OR_GREATER using System;
using System;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion; using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
namespace AMWD.Common.EntityFrameworkCore.Converters namespace AMWD.Common.EntityFrameworkCore.Converters
@@ -24,5 +22,3 @@ namespace AMWD.Common.EntityFrameworkCore.Converters
{ } { }
} }
} }
#endif

View File

@@ -1,6 +1,4 @@
#if NET6_0_OR_GREATER using System;
using System;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion; using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
namespace AMWD.Common.EntityFrameworkCore.Converters namespace AMWD.Common.EntityFrameworkCore.Converters
@@ -24,5 +22,3 @@ namespace AMWD.Common.EntityFrameworkCore.Converters
{ } { }
} }
} }
#endif

View File

@@ -33,6 +33,8 @@ namespace System
: base(message, innerException) : base(message, innerException)
{ } { }
#if NET6_0
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="DatabaseProviderException"/> class with serialized data. /// Initializes a new instance of the <see cref="DatabaseProviderException"/> class with serialized data.
/// </summary> /// </summary>
@@ -43,5 +45,7 @@ namespace System
protected DatabaseProviderException(SerializationInfo info, StreamingContext context) protected DatabaseProviderException(SerializationInfo info, StreamingContext context)
: base(info, context) : base(info, context)
{ } { }
#endif
} }
} }

View File

@@ -24,6 +24,7 @@ namespace Microsoft.EntityFrameworkCore
/// <param name="optionsAction">An action to set additional options.</param> /// <param name="optionsAction">An action to set additional options.</param>
/// <param name="cancellationToken">The cancellation token.</param> /// <param name="cancellationToken">The cancellation token.</param>
/// <returns>true on success, otherwise false or an exception is thrown.</returns> /// <returns>true on success, otherwise false or an exception is thrown.</returns>
[System.Diagnostics.CodeAnalysis.SuppressMessage("Usage", "CA2208")]
public static async Task<bool> ApplyMigrationsAsync(this DatabaseFacade database, Action<DatabaseMigrationOptions> optionsAction, CancellationToken cancellationToken = default) public static async Task<bool> ApplyMigrationsAsync(this DatabaseFacade database, Action<DatabaseMigrationOptions> optionsAction, CancellationToken cancellationToken = default)
{ {
if (database == null) if (database == null)
@@ -45,21 +46,21 @@ namespace Microsoft.EntityFrameworkCore
{ {
opts.WaitDelay = options.WaitDelay; opts.WaitDelay = options.WaitDelay;
opts.Logger = options.Logger; opts.Logger = options.Logger;
}, cancellationToken); }, cancellationToken).ConfigureAwait(false);
var connection = database.GetDbConnection(); var connection = database.GetDbConnection();
try try
{ {
await connection.OpenAsync(cancellationToken); await connection.OpenAsync(cancellationToken).ConfigureAwait(false);
if (!await connection.CreateMigrationsTable(options, cancellationToken)) if (!await connection.CreateMigrationsTable(options, cancellationToken).ConfigureAwait(false))
return false; return false;
return await connection.Migrate(options, cancellationToken); return await connection.Migrate(options, cancellationToken).ConfigureAwait(false);
} }
finally finally
{ {
await connection.CloseAsync(); await connection.CloseAsync().ConfigureAwait(false);
} }
} }
@@ -87,7 +88,7 @@ namespace Microsoft.EntityFrameworkCore
{ {
try try
{ {
await connection.OpenAsync(cancellationToken); await connection.OpenAsync(cancellationToken).ConfigureAwait(false);
options.Logger?.LogInformation("Database connection available"); options.Logger?.LogInformation("Database connection available");
return; return;
} }
@@ -96,7 +97,7 @@ namespace Microsoft.EntityFrameworkCore
// keep things quiet // keep things quiet
try try
{ {
await Task.Delay(options.WaitDelay, cancellationToken); await Task.Delay(options.WaitDelay, cancellationToken).ConfigureAwait(false);
} }
catch (OperationCanceledException) when (cancellationToken.IsCancellationRequested) catch (OperationCanceledException) when (cancellationToken.IsCancellationRequested)
{ {
@@ -109,7 +110,7 @@ namespace Microsoft.EntityFrameworkCore
} }
finally finally
{ {
await connection.CloseAsync(); await connection.CloseAsync().ConfigureAwait(false);
} }
} }
} }
@@ -192,7 +193,7 @@ BEGIN
END;" END;"
}; };
await command.ExecuteNonQueryAsync(cancellationToken); await command.ExecuteNonQueryAsync(cancellationToken).ConfigureAwait(false);
return true; return true;
} }
catch (Exception ex) catch (Exception ex)
@@ -235,9 +236,9 @@ END;"
DatabaseProvider.SQLServer => $"SELECT [schema_file] FROM [{options.MigrationsTableName}];", DatabaseProvider.SQLServer => $"SELECT [schema_file] FROM [{options.MigrationsTableName}];",
_ => $@"SELECT ""schema_file"" FROM ""{options.MigrationsTableName}"";", _ => $@"SELECT ""schema_file"" FROM ""{options.MigrationsTableName}"";",
}; };
using (var reader = await command.ExecuteReaderAsync(cancellationToken)) using (var reader = await command.ExecuteReaderAsync(cancellationToken).ConfigureAwait(false))
{ {
while (await reader.ReadAsync(cancellationToken)) while (await reader.ReadAsync(cancellationToken).ConfigureAwait(false))
migratedFiles.Add(reader.GetString(0)); migratedFiles.Add(reader.GetString(0));
} }
@@ -246,7 +247,7 @@ END;"
{ {
// remove path including the separator // remove path including the separator
string fileName = migrationFile.Replace(options.Path, "")[1..]; string fileName = migrationFile.Replace(options.Path, "")[1..];
using var transaction = await connection.BeginTransactionAsync(cancellationToken); using var transaction = await connection.BeginTransactionAsync(cancellationToken).ConfigureAwait(false);
try try
{ {
// max length in the database: 250 chars // max length in the database: 250 chars
@@ -263,13 +264,13 @@ END;"
string sqlScript = null; string sqlScript = null;
if (options.SourceAssembly == null) if (options.SourceAssembly == null)
{ {
sqlScript = await File.ReadAllTextAsync(migrationFile, cancellationToken); sqlScript = await File.ReadAllTextAsync(migrationFile, cancellationToken).ConfigureAwait(false);
} }
else else
{ {
using var stream = options.SourceAssembly.GetManifestResourceStream(migrationFile); using var stream = options.SourceAssembly.GetManifestResourceStream(migrationFile);
using var sr = new StreamReader(stream); using var sr = new StreamReader(stream);
sqlScript = await sr.ReadToEndAsync(); sqlScript = await sr.ReadToEndAsync().ConfigureAwait(false);
} }
if (string.IsNullOrWhiteSpace(sqlScript)) if (string.IsNullOrWhiteSpace(sqlScript))
@@ -278,7 +279,7 @@ END;"
options.Logger?.LogDebug($" Migrating file '{fileName}' started"); options.Logger?.LogDebug($" Migrating file '{fileName}' started");
command.Transaction = transaction; command.Transaction = transaction;
await command.ExecuteScript(sqlScript, cancellationToken); await command.ExecuteScript(sqlScript, cancellationToken).ConfigureAwait(false);
command.CommandText = connection.GetProviderType() switch command.CommandText = connection.GetProviderType() switch
{ {
@@ -286,15 +287,15 @@ END;"
DatabaseProvider.SQLServer => $"INSERT INTO [{options.MigrationsTableName}] ([schema_file], [installed_at]) VALUES ('{trimmedFileName.Replace("'", "\\'")}', '{DateTime.UtcNow:yyyy-MM-dd HH:mm}');", DatabaseProvider.SQLServer => $"INSERT INTO [{options.MigrationsTableName}] ([schema_file], [installed_at]) VALUES ('{trimmedFileName.Replace("'", "\\'")}', '{DateTime.UtcNow:yyyy-MM-dd HH:mm}');",
_ => $@"INSERT INTO ""{options.MigrationsTableName}"" (""schema_file"", ""installed_at"") VALUES ('{trimmedFileName.Replace("'", "\\'")}', '{DateTime.UtcNow:yyyy-MM-dd HH:mm}');", _ => $@"INSERT INTO ""{options.MigrationsTableName}"" (""schema_file"", ""installed_at"") VALUES ('{trimmedFileName.Replace("'", "\\'")}', '{DateTime.UtcNow:yyyy-MM-dd HH:mm}');",
}; };
await command.ExecuteNonQueryAsync(cancellationToken); await command.ExecuteNonQueryAsync(cancellationToken).ConfigureAwait(false);
await transaction.CommitAsync(cancellationToken); await transaction.CommitAsync(cancellationToken).ConfigureAwait(false);
command.Transaction = null; command.Transaction = null;
options.Logger?.LogDebug($" Migrating file '{fileName}' successful"); options.Logger?.LogDebug($" Migrating file '{fileName}' successful");
} }
catch (Exception ex) catch (Exception ex)
{ {
await transaction.RollbackAsync(cancellationToken); await transaction.RollbackAsync(cancellationToken).ConfigureAwait(false);
options.Logger?.LogError($"Migrating file '{fileName}' failed: {ex.InnerException?.Message ?? ex.Message}"); options.Logger?.LogError($"Migrating file '{fileName}' failed: {ex.InnerException?.Message ?? ex.Message}");
return false; return false;
} }
@@ -330,7 +331,7 @@ END;"
if (!string.IsNullOrWhiteSpace(pt)) if (!string.IsNullOrWhiteSpace(pt))
{ {
command.CommandText = pt; command.CommandText = pt;
affectedRows += await command.ExecuteNonQueryAsync(cancellationToken); affectedRows += await command.ExecuteNonQueryAsync(cancellationToken).ConfigureAwait(false);
} }
} }
return affectedRows; return affectedRows;
@@ -338,7 +339,7 @@ END;"
else else
{ {
command.CommandText = text; command.CommandText = text;
return await command.ExecuteNonQueryAsync(cancellationToken); return await command.ExecuteNonQueryAsync(cancellationToken).ConfigureAwait(false);
} }
} }

View File

@@ -3,9 +3,7 @@ using System.Reflection;
using System.Text; using System.Text;
using AMWD.Common.EntityFrameworkCore.Attributes; using AMWD.Common.EntityFrameworkCore.Attributes;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
#if NET6_0_OR_GREATER
using Microsoft.EntityFrameworkCore.Metadata; using Microsoft.EntityFrameworkCore.Metadata;
#endif
namespace AMWD.Common.EntityFrameworkCore.Extensions namespace AMWD.Common.EntityFrameworkCore.Extensions
{ {
@@ -35,13 +33,7 @@ namespace AMWD.Common.EntityFrameworkCore.Extensions
index.IsUnique = indexAttribute.IsUnique; index.IsUnique = indexAttribute.IsUnique;
if (!string.IsNullOrWhiteSpace(indexAttribute.Name)) if (!string.IsNullOrWhiteSpace(indexAttribute.Name))
{
#if NET6_0_OR_GREATER
index.SetDatabaseName(indexAttribute.Name.Trim()); index.SetDatabaseName(indexAttribute.Name.Trim());
#else
index.SetName(indexAttribute.Name.Trim());
#endif
}
} }
} }
} }
@@ -60,28 +52,14 @@ namespace AMWD.Common.EntityFrameworkCore.Extensions
{ {
// skip conversion when table name is explicitly set // skip conversion when table name is explicitly set
if ((entityType.ClrType.GetCustomAttribute(typeof(TableAttribute), false) as TableAttribute) == null) if ((entityType.ClrType.GetCustomAttribute(typeof(TableAttribute), false) as TableAttribute) == null)
{
#if NET6_0_OR_GREATER
entityType.SetTableName(ConvertToSnakeCase(entityType.GetTableName())); entityType.SetTableName(ConvertToSnakeCase(entityType.GetTableName()));
#else
entityType.SetTableName(ConvertToSnakeCase(entityType.GetTableName()));
#endif
}
#if NET6_0_OR_GREATER
var identifier = StoreObjectIdentifier.Table(entityType.GetTableName(), entityType.GetSchema()); var identifier = StoreObjectIdentifier.Table(entityType.GetTableName(), entityType.GetSchema());
#endif
foreach (var property in entityType.GetProperties()) foreach (var property in entityType.GetProperties())
{ {
// skip conversion when column name is explicitly set // skip conversion when column name is explicitly set
if ((entityType.ClrType.GetProperty(property.Name)?.GetCustomAttribute(typeof(ColumnAttribute), false) as ColumnAttribute) == null) if ((entityType.ClrType.GetProperty(property.Name)?.GetCustomAttribute(typeof(ColumnAttribute), false) as ColumnAttribute) == null)
{
#if NET6_0_OR_GREATER
property.SetColumnName(ConvertToSnakeCase(property.GetColumnName(identifier))); property.SetColumnName(ConvertToSnakeCase(property.GetColumnName(identifier)));
#else
property.SetColumnName(ConvertToSnakeCase(property.GetColumnName()));
#endif
}
} }
} }

View File

@@ -1,6 +1,4 @@
#if NET6_0_OR_GREATER using System;
using System;
using AMWD.Common.EntityFrameworkCore.Converters; using AMWD.Common.EntityFrameworkCore.Converters;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
@@ -12,14 +10,14 @@ namespace AMWD.Common.EntityFrameworkCore.Extensions
public static class ModelConfigurationBuilderExtensions public static class ModelConfigurationBuilderExtensions
{ {
/// <summary> /// <summary>
/// Adds converters for the <see cref="DateOnly"/> and <see cref="TimeOnly"/> datatypes introduced with .NET 6.0. /// Adds converters for the <see cref="DateOnly"/> datatype introduced with .NET 6.0.
/// </summary> /// </summary>
/// <remarks> /// <remarks>
/// As of 2022-06-04 only required for Microsoft SQL server on .NET 6.0. /// As of 2022-06-04 only required for Microsoft SQL server on .NET 6.0.
/// </remarks> /// </remarks>
/// <param name="builder">The <see cref="ModelConfigurationBuilder"/> instance.</param> /// <param name="builder">The <see cref="ModelConfigurationBuilder"/> instance.</param>
/// <returns>The <see cref="ModelConfigurationBuilder"/> instance after applying the converters.</returns> /// <returns>The <see cref="ModelConfigurationBuilder"/> instance after applying the converters.</returns>
public static ModelConfigurationBuilder AddDateOnlyTimeOnlyConverters(this ModelConfigurationBuilder builder) public static ModelConfigurationBuilder AddDateOnlyConverters(this ModelConfigurationBuilder builder)
{ {
builder.Properties<DateOnly>() builder.Properties<DateOnly>()
.HaveConversion<DateOnlyConverter>() .HaveConversion<DateOnlyConverter>()
@@ -28,6 +26,19 @@ namespace AMWD.Common.EntityFrameworkCore.Extensions
.HaveConversion<NullableDateOnlyConverter>() .HaveConversion<NullableDateOnlyConverter>()
.HaveColumnType("date"); .HaveColumnType("date");
return builder;
}
/// <summary>
/// Adds converters for the <see cref="TimeOnly"/> datatype introduced with .NET 6.0.
/// </summary>
/// <remarks>
/// As of 2022-06-04 only required for Microsoft SQL server on .NET 6.0.
/// </remarks>
/// <param name="builder">The <see cref="ModelConfigurationBuilder"/> instance.</param>
/// <returns>The <see cref="ModelConfigurationBuilder"/> instance after applying the converters.</returns>
public static ModelConfigurationBuilder AddTimeOnlyConverters(this ModelConfigurationBuilder builder)
{
builder.Properties<TimeOnly>() builder.Properties<TimeOnly>()
.HaveConversion<TimeOnlyConverter>() .HaveConversion<TimeOnlyConverter>()
.HaveColumnType("time"); .HaveColumnType("time");
@@ -39,5 +50,3 @@ namespace AMWD.Common.EntityFrameworkCore.Extensions
} }
} }
} }
#endif

View File

@@ -0,0 +1,3 @@
using System.Diagnostics.CodeAnalysis;
[assembly: SuppressMessage("Usage", "CA2254")]

View File

@@ -1,39 +0,0 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<Configurations>Debug;Release;DebugLocal</Configurations>
<TargetFramework>netstandard2.0</TargetFramework>
<LangVersion>10.0</LangVersion>
<AssemblyName>AMWD.Common.Moq</AssemblyName>
<RootNamespace>AMWD.Common.Moq</RootNamespace>
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
<PackageId>AMWD.Common.Moq</PackageId>
<PackageIcon>icon.png</PackageIcon>
<Product>AM.WD Common Library for Moq</Product>
</PropertyGroup>
<PropertyGroup Condition="'$(GITLAB_CI)' == 'true'">
<ContinuousIntegrationBuild>true</ContinuousIntegrationBuild>
</PropertyGroup>
<ItemGroup Condition="'$(GITLAB_CI)' == 'true'">
<SourceLinkGitLabHost Include="git.am-wd.de" Version="$(CI_SERVER_VERSION)" />
<PackageReference Include="Microsoft.SourceLink.GitLab" Version="1.1.1" PrivateAssets="all" />
</ItemGroup>
<ItemGroup>
<None Include="../icon.png" Pack="true" PackagePath="/" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Moq" Version="4.18.4" />
<PackageReference Include="Unclassified.NetRevisionTask" Version="0.4.3">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
</ItemGroup>
</Project>

View File

@@ -0,0 +1,29 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
<LangVersion>10.0</LangVersion>
<NrtTagMatch>test/v[0-9]*</NrtTagMatch>
<AssemblyName>AMWD.Common.Test</AssemblyName>
<RootNamespace>AMWD.Common.Test</RootNamespace>
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
<PackageId>AMWD.Common.Test</PackageId>
<PackageIcon>icon.png</PackageIcon>
<PackageReadmeFile>README.md</PackageReadmeFile>
<Product>AM.WD Common Library for Unit-Testing</Product>
</PropertyGroup>
<ItemGroup>
<None Include="../icon.png" Pack="true" PackagePath="/" />
<None Include="../README.md" Pack="true" PackagePath="/" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Moq" Version="4.20.69" />
<PackageReference Include="MSTest.TestFramework" Version="3.1.1" />
</ItemGroup>
</Project>

View File

@@ -8,7 +8,7 @@ using System.Threading.Tasks;
using Moq; using Moq;
using Moq.Protected; using Moq.Protected;
namespace AMWD.Common.Moq namespace AMWD.Common.Test
{ {
/// <summary> /// <summary>
/// Wrapps the <see cref="Mock{HttpMessageHandler}"/> including the setup. /// Wrapps the <see cref="Mock{HttpMessageHandler}"/> including the setup.
@@ -39,8 +39,8 @@ namespace AMWD.Common.Moq
if (req.Content != null) if (req.Content != null)
{ {
callback.ContentBytes = await req.Content.ReadAsByteArrayAsync(); callback.ContentBytes = await req.Content.ReadAsByteArrayAsync().ConfigureAwait(false);
callback.ContentString = await req.Content.ReadAsStringAsync(); callback.ContentString = await req.Content.ReadAsStringAsync().ConfigureAwait(false);
} }
Callbacks.Add(callback); Callbacks.Add(callback);

View File

@@ -0,0 +1,87 @@
using System.IO;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Text;
using Microsoft.VisualStudio.TestTools.UnitTesting;
namespace AMWD.Common.Test
{
/// <summary>
/// Implements a snapshot comparison for content aggregation (e.g. files).
/// </summary>
public sealed class SnapshotAssert
{
/// <summary>
/// Tests whether the specified string is equal to the saved snapshot.
/// </summary>
/// <param name="actual">The current aggregated content string.</param>
/// <param name="message">An error message.</param>
/// <param name="callerFilePath">The absolute file path of the calling file (filled automatically on compile time).</param>
/// <param name="callerMemberName">The name of the calling method (filled automatically on compile time).</param>
public static void AreEqual(string actual, string message = null, [CallerFilePath] string callerFilePath = null, [CallerMemberName] string callerMemberName = null)
{
string cleanLineEnding = actual
.Replace("\r\n", "\n") // Windows
.Replace("\r", "\n"); // old MacOS
AreEqual(Encoding.UTF8.GetBytes(cleanLineEnding), message, callerFilePath, callerMemberName);
}
/// <summary>
/// Tests whether the specified byte array is equal to the saved snapshot.
/// </summary>
/// <param name="actual">The current aggregated content bytes.</param>
/// <param name="message">An error message.</param>
/// <param name="callerFilePath">The absolute file path of the calling file (filled automatically on compile time).</param>
/// <param name="callerMemberName">The name of the calling method (filled automatically on compile time).</param>
public static void AreEqual(byte[] actual, string message = null, [CallerFilePath] string callerFilePath = null, [CallerMemberName] string callerMemberName = null)
=> AreEqual(actual, 0, -1, message, callerFilePath, callerMemberName);
/// <summary>
/// Tests whether the specified byte array is equal to the saved snapshot.
/// </summary>
/// <remarks>
/// The past has shown, that e.g. wkhtmltopdf prints the current timestamp at the beginning of the PDF file.
/// Therefore only a specific part of that file can be asserted to be equal.
/// </remarks>
/// <param name="actual">The current aggregated content bytes.</param>
/// <param name="firstByteIndex">The first byte to compare.</param>
/// <param name="lastByteIndex">The last byte to compare.</param>
/// <param name="message">An error message.</param>
/// <param name="callerFilePath">The absolute file path of the calling file (filled automatically on compile time).</param>
/// <param name="callerMemberName">The name of the calling method (filled automatically on compile time).</param>
public static void AreEqual(byte[] actual, int firstByteIndex, int lastByteIndex, string message = null, [CallerFilePath] string callerFilePath = null, [CallerMemberName] string callerMemberName = null)
{
string callerDir = Path.GetDirectoryName(callerFilePath);
string callerFile = Path.GetFileNameWithoutExtension(callerFilePath);
string snapshotDir = Path.Combine(callerDir, "Snapshots", callerFile);
string snapshotFile = Path.Combine(snapshotDir, $"{callerMemberName}.snap");
if (File.Exists(snapshotFile))
{
byte[] expected = File.ReadAllBytes(snapshotFile);
var actualBytes = actual.Skip(firstByteIndex);
var expectedBytes = expected.Skip(firstByteIndex);
if (lastByteIndex > firstByteIndex)
{
actualBytes = actualBytes.Take(lastByteIndex - firstByteIndex);
expectedBytes = expectedBytes.Take(lastByteIndex - firstByteIndex);
}
if (message == null)
CollectionAssert.AreEqual(expectedBytes.ToArray(), actualBytes.ToArray());
else
CollectionAssert.AreEqual(expectedBytes.ToArray(), actualBytes.ToArray(), message);
}
else
{
if (!Directory.Exists(snapshotDir))
Directory.CreateDirectory(snapshotDir);
File.WriteAllBytes(snapshotFile, actual);
}
}
}
}

View File

@@ -5,14 +5,14 @@ using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using Moq; using Moq;
namespace AMWD.Common.Moq namespace AMWD.Common.Test
{ {
/// <summary> /// <summary>
/// Wrapps the <see cref="Mock{TcpClient}"/> including the setup. /// Wrapps the <see cref="Mock{TcpClient}"/> including the setup.
/// </summary> /// </summary>
public class TcpClientMoq public class TcpClientMoq
{ {
private readonly Mock<NetworkStream> streamMock; private readonly Mock<NetworkStream> _streamMock;
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="TcpClientMoq"/> class. /// Initializes a new instance of the <see cref="TcpClientMoq"/> class.
@@ -22,8 +22,8 @@ namespace AMWD.Common.Moq
Callbacks = new(); Callbacks = new();
Response = new byte[0]; Response = new byte[0];
streamMock = new(); _streamMock = new();
streamMock _streamMock
.Setup(s => s.WriteAsync(It.IsAny<byte[]>(), It.IsAny<int>(), It.IsAny<int>(), It.IsAny<CancellationToken>())) .Setup(s => s.WriteAsync(It.IsAny<byte[]>(), It.IsAny<int>(), It.IsAny<int>(), It.IsAny<CancellationToken>()))
.Callback<byte[], int, int, CancellationToken>((buffer, offset, count, _) => .Callback<byte[], int, int, CancellationToken>((buffer, offset, count, _) =>
{ {
@@ -39,7 +39,7 @@ namespace AMWD.Common.Moq
Callbacks.Add(callback); Callbacks.Add(callback);
}) })
.Returns(Task.CompletedTask); .Returns(Task.CompletedTask);
streamMock _streamMock
.Setup(s => s.Write(It.IsAny<byte[]>(), It.IsAny<int>(), It.IsAny<int>())) .Setup(s => s.Write(It.IsAny<byte[]>(), It.IsAny<int>(), It.IsAny<int>()))
.Callback<byte[], int, int>((buffer, offset, count) => .Callback<byte[], int, int>((buffer, offset, count) =>
{ {
@@ -55,7 +55,7 @@ namespace AMWD.Common.Moq
Callbacks.Add(callback); Callbacks.Add(callback);
}); });
streamMock _streamMock
.Setup(s => s.ReadAsync(It.IsAny<byte[]>(), It.IsAny<int>(), It.IsAny<int>(), It.IsAny<CancellationToken>())) .Setup(s => s.ReadAsync(It.IsAny<byte[]>(), It.IsAny<int>(), It.IsAny<int>(), It.IsAny<CancellationToken>()))
.Callback<byte[], int, int, CancellationToken>((buffer, offset, count, _) => .Callback<byte[], int, int, CancellationToken>((buffer, offset, count, _) =>
{ {
@@ -63,7 +63,7 @@ namespace AMWD.Common.Moq
Array.Copy(bytes, 0, buffer, offset, Math.Min(bytes.Length, count)); Array.Copy(bytes, 0, buffer, offset, Math.Min(bytes.Length, count));
}) })
.ReturnsAsync(Response?.Length ?? 0); .ReturnsAsync(Response?.Length ?? 0);
streamMock _streamMock
.Setup(s => s.Read(It.IsAny<byte[]>(), It.IsAny<int>(), It.IsAny<int>())) .Setup(s => s.Read(It.IsAny<byte[]>(), It.IsAny<int>(), It.IsAny<int>()))
.Callback<byte[], int, int>((buffer, offset, count) => .Callback<byte[], int, int>((buffer, offset, count) =>
{ {
@@ -75,7 +75,7 @@ namespace AMWD.Common.Moq
Mock = new(); Mock = new();
Mock Mock
.Setup(c => c.GetStream()) .Setup(c => c.GetStream())
.Returns(streamMock.Object); .Returns(_streamMock.Object);
} }
/// <summary> /// <summary>
@@ -107,28 +107,28 @@ namespace AMWD.Common.Moq
/// </summary> /// </summary>
/// <param name="times">Number of calls.</param> /// <param name="times">Number of calls.</param>
public void VerifyWriteAsync(Times times) public void VerifyWriteAsync(Times times)
=> streamMock.Verify(s => s.WriteAsync(It.IsAny<byte[]>(), It.IsAny<int>(), It.IsAny<int>(), It.IsAny<CancellationToken>()), times); => _streamMock.Verify(s => s.WriteAsync(It.IsAny<byte[]>(), It.IsAny<int>(), It.IsAny<int>(), It.IsAny<CancellationToken>()), times);
/// <summary> /// <summary>
/// Verifies the number of calls writing synchronous to the stream. /// Verifies the number of calls writing synchronous to the stream.
/// </summary> /// </summary>
/// <param name="times">Number of calls.</param> /// <param name="times">Number of calls.</param>
public void VerifyWriteSync(Times times) public void VerifyWriteSync(Times times)
=> streamMock.Verify(s => s.Write(It.IsAny<byte[]>(), It.IsAny<int>(), It.IsAny<int>()), times); => _streamMock.Verify(s => s.Write(It.IsAny<byte[]>(), It.IsAny<int>(), It.IsAny<int>()), times);
/// <summary> /// <summary>
/// Verifies the number of calls reading asynchronous from the stream. /// Verifies the number of calls reading asynchronous from the stream.
/// </summary> /// </summary>
/// <param name="times">Number of calls.</param> /// <param name="times">Number of calls.</param>
public void VerifyReadAsync(Times times) public void VerifyReadAsync(Times times)
=> streamMock.Verify(s => s.ReadAsync(It.IsAny<byte[]>(), It.IsAny<int>(), It.IsAny<int>(), It.IsAny<CancellationToken>()), times); => _streamMock.Verify(s => s.ReadAsync(It.IsAny<byte[]>(), It.IsAny<int>(), It.IsAny<int>(), It.IsAny<CancellationToken>()), times);
/// <summary> /// <summary>
/// Verifies the number of calls reading synchronous from the stream. /// Verifies the number of calls reading synchronous from the stream.
/// </summary> /// </summary>
/// <param name="times">Number of calls.</param> /// <param name="times">Number of calls.</param>
public void VerifyReadSync(Times times) public void VerifyReadSync(Times times)
=> streamMock.Verify(s => s.Read(It.IsAny<byte[]>(), It.IsAny<int>(), It.IsAny<int>()), times); => _streamMock.Verify(s => s.Read(It.IsAny<byte[]>(), It.IsAny<int>(), It.IsAny<int>()), times);
/// <summary> /// <summary>
/// Represents the placed TCP request. /// Represents the placed TCP request.

View File

@@ -1,7 +1,6 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<Configurations>Debug;Release;DebugLocal</Configurations>
<TargetFramework>netstandard2.0</TargetFramework> <TargetFramework>netstandard2.0</TargetFramework>
<LangVersion>10.0</LangVersion> <LangVersion>10.0</LangVersion>
@@ -11,32 +10,21 @@
<GeneratePackageOnBuild>true</GeneratePackageOnBuild> <GeneratePackageOnBuild>true</GeneratePackageOnBuild>
<PackageId>AMWD.Common</PackageId> <PackageId>AMWD.Common</PackageId>
<PackageIcon>icon.png</PackageIcon> <PackageIcon>icon.png</PackageIcon>
<PackageReadmeFile>README.md</PackageReadmeFile>
<Product>AM.WD Common Library</Product> <Product>AM.WD Common Library</Product>
</PropertyGroup> </PropertyGroup>
<PropertyGroup Condition="'$(GITLAB_CI)' == 'true'">
<ContinuousIntegrationBuild>true</ContinuousIntegrationBuild>
</PropertyGroup>
<ItemGroup Condition="'$(GITLAB_CI)' == 'true'">
<SourceLinkGitLabHost Include="git.am-wd.de" Version="$(CI_SERVER_VERSION)" />
<PackageReference Include="Microsoft.SourceLink.GitLab" Version="1.1.1" PrivateAssets="all" />
</ItemGroup>
<ItemGroup> <ItemGroup>
<None Include="../icon.png" Pack="true" PackagePath="/" /> <None Include="../icon.png" Pack="true" PackagePath="/" />
<None Include="../README.md" Pack="true" PackagePath="/" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="MessagePack" Version="2.4.35" /> <PackageReference Include="MessagePack" Version="2.5.129" />
<PackageReference Include="Microsoft.AspNetCore.HttpOverrides" Version="2.2.0" /> <PackageReference Include="Microsoft.AspNetCore.HttpOverrides" Version="2.2.0" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.1" /> <PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
<PackageReference Include="Unclassified.DeepConvert" Version="1.4.0" /> <PackageReference Include="Unclassified.DeepConvert" Version="1.4.0" />
<PackageReference Include="Unclassified.NetRevisionTask" Version="0.4.3">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
</ItemGroup> </ItemGroup>
</Project> </Project>

View File

@@ -0,0 +1,36 @@
namespace AMWD.Common.Cli
{
/// <summary>
/// Represents a logical argument in the command line. Options with their additional
/// parameters are combined in one argument.
/// </summary>
[System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage]
public class Argument
{
/// <summary>
/// Initialises a new instance of the <see cref="Argument"/> class.
/// </summary>
/// <param name="option">The <see cref="Option"/> that is set in this argument; or null.</param>
/// <param name="values">The additional parameter values for the option; or the argument value.</param>
internal Argument(Option option, string[] values)
{
Option = option;
Values = values;
}
/// <summary>
/// Gets the <see cref="Option"/> that is set in this argument; or null.
/// </summary>
public Option Option { get; private set; }
/// <summary>
/// Gets the additional parameter values for the option; or the argument value.
/// </summary>
public string[] Values { get; private set; }
/// <summary>
/// Gets the first item of <see cref="Values"/>; or null.
/// </summary>
public string Value => Values.Length > 0 ? Values[0] : null;
}
}

View File

@@ -0,0 +1,366 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace AMWD.Common.Cli
{
/// <summary>
/// Provides options and arguments parsing from command line arguments or a single string.
/// </summary>
public class CommandLineParser
{
#region Private data
private string[] _args;
private List<Argument> _parsedArguments;
private readonly List<Option> _options = new();
#endregion Private data
#region Configuration properties
/// <summary>
/// Gets or sets a value indicating whether the option names are case-sensitive.
/// (Default: false)
/// </summary>
public bool IsCaseSensitive { get; set; }
/// <summary>
/// Gets or sets a value indicating whether incomplete options can be automatically
/// completed if there is only a single matching option.
/// (Default: true)
/// </summary>
public bool AutoCompleteOptions { get; set; } = true;
#endregion Configuration properties
#region Custom arguments line parsing
// Source: http://stackoverflow.com/a/23961658/143684
/// <summary>
/// Parses a single string into an arguments array.
/// </summary>
/// <param name="argsString">The string that contains the entire command line.</param>
public static string[] ParseArgsString(string argsString)
{
// Collects the split argument strings
var args = new List<string>();
// Builds the current argument
var currentArg = new StringBuilder();
// Indicates whether the last character was a backslash escape character
bool escape = false;
// Indicates whether we're in a quoted range
bool inQuote = false;
// Indicates whether there were quotes in the current arguments
bool hadQuote = false;
// Remembers the previous character
char prevCh = '\0';
// Iterate all characters from the input string
for (int i = 0; i < argsString.Length; i++)
{
char ch = argsString[i];
if (ch == '\\' && !escape)
{
// Beginning of a backslash-escape sequence
escape = true;
}
else if (ch == '\\' && escape)
{
// Double backslash, keep one
currentArg.Append(ch);
escape = false;
}
else if (ch == '"' && !escape)
{
// Toggle quoted range
inQuote = !inQuote;
hadQuote = true;
if (inQuote && prevCh == '"')
{
// Doubled quote within a quoted range is like escaping
currentArg.Append(ch);
}
}
else if (ch == '"' && escape)
{
// Backslash-escaped quote, keep it
currentArg.Append(ch);
escape = false;
}
else if (char.IsWhiteSpace(ch) && !inQuote)
{
if (escape)
{
// Add pending escape char
currentArg.Append('\\');
escape = false;
}
// Accept empty arguments only if they are quoted
if (currentArg.Length > 0 || hadQuote)
{
args.Add(currentArg.ToString());
}
// Reset for next argument
currentArg.Clear();
hadQuote = false;
}
else
{
if (escape)
{
// Add pending escape char
currentArg.Append('\\');
escape = false;
}
// Copy character from input, no special meaning
currentArg.Append(ch);
}
prevCh = ch;
}
// Save last argument
if (currentArg.Length > 0 || hadQuote)
{
args.Add(currentArg.ToString());
}
return args.ToArray();
}
/// <summary>
/// Reads the command line arguments from a single string.
/// </summary>
/// <param name="argsString">The string that contains the entire command line.</param>
public void ReadArgs(string argsString)
{
_args = ParseArgsString(argsString);
}
#endregion Custom arguments line parsing
#region Options management
/// <summary>
/// Registers a named option without additional parameters.
/// </summary>
/// <param name="name">The option name.</param>
/// <returns>The option instance.</returns>
public Option RegisterOption(string name)
{
return RegisterOption(name, 0);
}
/// <summary>
/// Registers a named option.
/// </summary>
/// <param name="name">The option name.</param>
/// <param name="parameterCount">The number of additional parameters for this option.</param>
/// <returns>The option instance.</returns>
public Option RegisterOption(string name, int parameterCount)
{
var option = new Option(name, parameterCount);
_options.Add(option);
return option;
}
#endregion Options management
#region Parsing method
/// <summary>
/// Parses all command line arguments.
/// </summary>
/// <param name="args">The command line arguments.</param>
public void Parse(string[] args)
{
_args = args ?? throw new ArgumentNullException(nameof(args));
Parse();
}
/// <summary>
/// Parses all command line arguments.
/// </summary>
public void Parse()
{
// Use args of the current process if no other source was given
if (_args == null)
{
_args = Environment.GetCommandLineArgs();
if (_args.Length > 0)
{
// Skip myself (args[0])
_args = _args.Skip(1).ToArray();
}
}
// Clear/reset data
_parsedArguments = new();
foreach (var option in _options)
{
option.IsSet = false;
option.SetCount = 0;
option.Argument = null;
}
var comparison = IsCaseSensitive
? StringComparison.Ordinal
: StringComparison.OrdinalIgnoreCase;
var argumentWalker = new EnumerableWalker<string>(_args);
bool optMode = true;
foreach (string arg in argumentWalker.Cast<string>())
{
if (arg == "--")
{
optMode = false;
}
else if (optMode && (arg.StartsWith("/") || arg.StartsWith("-")))
{
string optName = arg.Substring(arg.StartsWith("--") ? 2 : 1);
// Split option value if separated with : or = instead of whitespace
int separatorIndex = optName.IndexOfAny(new[] { ':', '=' });
string optValue = null;
if (separatorIndex != -1)
{
optValue = optName.Substring(separatorIndex + 1);
optName = optName.Substring(0, separatorIndex);
}
// Find the option with complete name match
var option = _options.FirstOrDefault(o => o.Names.Any(n => n.Equals(optName, comparison)));
if (option == null)
{
// Try to complete the name to a unique registered option
var matchingOptions = _options.Where(o => o.Names.Any(n => n.StartsWith(optName, comparison))).ToList();
if (AutoCompleteOptions && matchingOptions.Count > 1)
throw new Exception("Invalid option, completion is not unique: " + arg);
if (!AutoCompleteOptions || matchingOptions.Count == 0)
throw new Exception("Unknown option: " + arg);
// Accept the single auto-completed option
option = matchingOptions[0];
}
// Check for single usage
if (option.IsSingle && option.IsSet)
throw new Exception("Option cannot be set multiple times: " + arg);
// Collect option values from next argument strings
string[] values = new string[option.ParameterCount];
for (int i = 0; i < option.ParameterCount; i++)
{
if (optValue != null)
{
// The first value was included in this argument string
values[i] = optValue;
optValue = null;
}
else
{
// Fetch another argument string
values[i] = argumentWalker.GetNext();
}
if (values[i] == null)
throw new Exception("Missing argument " + (i + 1) + " for option: " + arg);
}
var argument = new Argument(option, values);
// Set usage data on the option instance for quick access
option.IsSet = true;
option.SetCount++;
option.Argument = argument;
if (option.Action != null)
{
option.Action(argument);
}
else
{
_parsedArguments.Add(argument);
}
}
else
{
_parsedArguments.Add(new Argument(null, new[] { arg }));
}
}
var missingOption = _options.FirstOrDefault(o => o.IsRequired && !o.IsSet);
if (missingOption != null)
throw new Exception("Missing required option: /" + missingOption.Names[0]);
}
#endregion Parsing method
#region Parsed data properties
/// <summary>
/// Gets the parsed arguments.
/// </summary>
/// <remarks>
/// To avoid exceptions thrown, call the <see cref="Parse()"/> method in advance for
/// exception handling.
/// </remarks>
public Argument[] Arguments
{
get
{
if (_parsedArguments == null)
Parse();
return _parsedArguments.ToArray();
}
}
/// <summary>
/// Gets the options that are set in the command line, including their value.
/// </summary>
/// <remarks>
/// To avoid exceptions thrown, call the <see cref="Parse()"/> method in advance for
/// exception handling.
/// </remarks>
public Option[] SetOptions
{
get
{
if (_parsedArguments == null)
Parse();
return _parsedArguments
.Where(a => a.Option != null)
.Select(a => a.Option)
.ToArray();
}
}
/// <summary>
/// Gets the free arguments that are set in the command line and don't belong to an option.
/// </summary>
/// <remarks>
/// To avoid exceptions thrown, call the <see cref="Parse()"/> method in advance for
/// exception handling.
/// </remarks>
public string[] FreeArguments
{
get
{
if (_parsedArguments == null)
Parse();
return _parsedArguments
.Where(a => a.Option == null)
.Select(a => a.Value)
.ToArray();
}
}
#endregion Parsed data properties
}
}

View File

@@ -0,0 +1,58 @@
using System;
using System.Collections;
using System.Collections.Generic;
namespace AMWD.Common.Cli
{
/// <summary>
/// Walks through an <see cref="IEnumerable{T}"/> and allows retrieving additional items.
/// </summary>
/// <typeparam name="T"></typeparam>
internal class EnumerableWalker<T> : IEnumerable<T>
where T : class
{
private readonly IEnumerable<T> _array;
private IEnumerator<T> _enumerator;
/// <summary>
/// Initialises a new instance of the <see cref="EnumerableWalker{T}"/> class.
/// </summary>
/// <param name="array">The array to walk though.</param>
public EnumerableWalker(IEnumerable<T> array)
{
_array = array ?? throw new ArgumentNullException(nameof(array));
}
IEnumerator<T> IEnumerable<T>.GetEnumerator()
{
_enumerator = _array.GetEnumerator();
return _enumerator;
}
/// <summary>
/// Gets the enumerator.
/// </summary>
/// <returns>The enumerator.</returns>
public IEnumerator GetEnumerator()
{
_enumerator = _array.GetEnumerator();
return _enumerator;
}
/// <summary>
/// Gets the next item.
/// </summary>
/// <returns>The next item.</returns>
public T GetNext()
{
if (_enumerator.MoveNext())
{
return _enumerator.Current;
}
else
{
return default;
}
}
}
}

113
AMWD.Common/Cli/Option.cs Normal file
View File

@@ -0,0 +1,113 @@
using System;
using System.Collections.Generic;
namespace AMWD.Common.Cli
{
/// <summary>
/// Represents a named option.
/// </summary>
[System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage]
public class Option
{
/// <summary>
/// Initialises a new instance of the <see cref="Option"/> class.
/// </summary>
/// <param name="name">The primary name of the option.</param>
/// <param name="parameterCount">The number of additional parameters for this option.</param>
internal Option(string name, int parameterCount)
{
Names = new List<string>() { name };
ParameterCount = parameterCount;
}
/// <summary>
/// Gets the names of this option.
/// </summary>
public List<string> Names { get; private set; }
/// <summary>
/// Gets the number of additional parameters for this option.
/// </summary>
public int ParameterCount { get; private set; }
/// <summary>
/// Gets a value indicating whether this option is required.
/// </summary>
public bool IsRequired { get; private set; }
/// <summary>
/// Gets a value indicating whether this option can only be specified once.
/// </summary>
public bool IsSingle { get; private set; }
/// <summary>
/// Gets the action to invoke when the option is set.
/// </summary>
public Action<Argument> Action { get; private set; }
/// <summary>
/// Gets a value indicating whether this option is set in the command line.
/// </summary>
public bool IsSet { get; internal set; }
/// <summary>
/// Gets the number of times that this option is set in the command line.
/// </summary>
public int SetCount { get; internal set; }
/// <summary>
/// Gets the <see cref="Argument"/> instance that contains additional parameters set
/// for this option.
/// </summary>
public Argument Argument { get; internal set; }
/// <summary>
/// Gets the value of the <see cref="Argument"/> instance for this option.
/// </summary>
public string Value => Argument?.Value;
/// <summary>
/// Sets alias names for this option.
/// </summary>
/// <param name="names">The alias names for this option.</param>
/// <returns>The current <see cref="Option"/> instance.</returns>
public Option Alias(params string[] names)
{
Names.AddRange(names);
return this;
}
/// <summary>
/// Marks this option as required. If a required option is not set in the command line,
/// an exception is thrown on parsing.
/// </summary>
/// <returns>The current <see cref="Option"/> instance.</returns>
public Option Required()
{
IsRequired = true;
return this;
}
/// <summary>
/// Marks this option as single. If a single option is set multiple times in the
/// command line, an exception is thrown on parsing.
/// </summary>
/// <returns>The current <see cref="Option"/> instance.</returns>
public Option Single()
{
IsSingle = true;
return this;
}
/// <summary>
/// Sets the action to invoke when the option is set.
/// </summary>
/// <param name="action">The action to invoke when the option is set.</param>
/// <returns>The current <see cref="Option"/> instance.</returns>
public Option Do(Action<Argument> action)
{
Action = action;
return this;
}
}
}

View File

@@ -17,7 +17,7 @@ namespace Newtonsoft.Json
/// <summary> /// <summary>
/// Common JSON serializer settings. /// Common JSON serializer settings.
/// </summary> /// </summary>
private static readonly JsonSerializerSettings jsonSerializerSettings = new() private static readonly JsonSerializerSettings _jsonSerializerSettings = new()
{ {
ContractResolver = new CamelCasePropertyNamesContractResolver(), ContractResolver = new CamelCasePropertyNamesContractResolver(),
Culture = CultureInfo.InvariantCulture Culture = CultureInfo.InvariantCulture
@@ -32,7 +32,7 @@ namespace Newtonsoft.Json
public static void DeserializeJson<T>(this T target, string json) public static void DeserializeJson<T>(this T target, string json)
{ {
if (!string.IsNullOrWhiteSpace(json)) if (!string.IsNullOrWhiteSpace(json))
JsonConvert.PopulateObject(json, target, jsonSerializerSettings); JsonConvert.PopulateObject(json, target, _jsonSerializerSettings);
} }
/// <summary> /// <summary>
@@ -42,7 +42,7 @@ namespace Newtonsoft.Json
/// <param name="json">The JSON string to read the values from.</param> /// <param name="json">The JSON string to read the values from.</param>
/// <returns>A new instance of <typeparamref name="T"/> with the deserialized values.</returns> /// <returns>A new instance of <typeparamref name="T"/> with the deserialized values.</returns>
public static T DeserializeJson<T>(this string json) public static T DeserializeJson<T>(this string json)
=> JsonConvert.DeserializeObject<T>(json, jsonSerializerSettings); => JsonConvert.DeserializeObject<T>(json, _jsonSerializerSettings);
/// <summary> /// <summary>
/// Deserializes a JSON string into a new instance or using the fallback value. /// Deserializes a JSON string into a new instance or using the fallback value.
@@ -55,7 +55,7 @@ namespace Newtonsoft.Json
{ {
try try
{ {
return JsonConvert.DeserializeObject<T>(json, jsonSerializerSettings); return JsonConvert.DeserializeObject<T>(json, _jsonSerializerSettings);
} }
catch catch
{ {
@@ -83,7 +83,7 @@ namespace Newtonsoft.Json
jw.QuoteChar = '\''; jw.QuoteChar = '\'';
jw.Formatting = indented ? Formatting.Indented : Formatting.None; jw.Formatting = indented ? Formatting.Indented : Formatting.None;
var serializer = useCamelCase ? JsonSerializer.Create(jsonSerializerSettings) : JsonSerializer.CreateDefault(); var serializer = useCamelCase ? JsonSerializer.Create(_jsonSerializerSettings) : JsonSerializer.CreateDefault();
serializer.Error += (s, a) => serializer.Error += (s, a) =>
{ {
@@ -107,7 +107,7 @@ namespace Newtonsoft.Json
if (obj == null) if (obj == null)
return null; return null;
var serializer = JsonSerializer.Create(jsonSerializerSettings); var serializer = JsonSerializer.Create(_jsonSerializerSettings);
return JObject.FromObject(obj, serializer); return JObject.FromObject(obj, serializer);
} }
@@ -121,7 +121,7 @@ namespace Newtonsoft.Json
if (array == null) if (array == null)
return null; return null;
var serializer = JsonSerializer.Create(jsonSerializerSettings); var serializer = JsonSerializer.Create(_jsonSerializerSettings);
return JArray.FromObject(array, serializer); return JArray.FromObject(array, serializer);
} }
@@ -146,13 +146,26 @@ namespace Newtonsoft.Json
if (lvlObj == null) if (lvlObj == null)
return defaultValue; return defaultValue;
lvlObj = lvlObj[level]; string lvl = level;
if (lvlObj.Type == JTokenType.Object)
{
foreach (var prop in ((JObject)lvlObj).Properties())
{
if (prop.Name.Equals(lvl, System.StringComparison.OrdinalIgnoreCase))
{
lvl = prop.Name;
break;
}
}
}
lvlObj = lvlObj[lvl];
} }
if (lvlObj == null) if (lvlObj == null)
return defaultValue; return defaultValue;
return DeepConvert.ChangeType<T>(lvlObj is JValue ? ((JValue)lvlObj).Value : lvlObj.Value<object>()); return DeepConvert.ChangeType<T>(lvlObj is JValue lvlValue ? lvlValue.Value : lvlObj.Value<object>());
} }
/// <summary> /// <summary>

View File

@@ -58,30 +58,30 @@
private struct DisposableReadWriteLock : IDisposable private struct DisposableReadWriteLock : IDisposable
{ {
private readonly ReaderWriterLockSlim rwLock; private readonly ReaderWriterLockSlim _rwLock;
private LockMode lockMode; private LockMode _lockMode;
public DisposableReadWriteLock(ReaderWriterLockSlim rwLock, LockMode lockMode) public DisposableReadWriteLock(ReaderWriterLockSlim rwLock, LockMode lockMode)
{ {
this.rwLock = rwLock; _rwLock = rwLock;
this.lockMode = lockMode; _lockMode = lockMode;
} }
public void Dispose() public void Dispose()
{ {
if (lockMode == LockMode.Read) if (_lockMode == LockMode.Read)
rwLock.ExitReadLock(); _rwLock.ExitReadLock();
if (lockMode == LockMode.Upgradable && rwLock.IsWriteLockHeld) // Upgraded with EnterWriteLock alone if (_lockMode == LockMode.Upgradable && _rwLock.IsWriteLockHeld) // Upgraded with EnterWriteLock alone
rwLock.ExitWriteLock(); _rwLock.ExitWriteLock();
if (lockMode == LockMode.Upgradable) if (_lockMode == LockMode.Upgradable)
rwLock.ExitUpgradeableReadLock(); _rwLock.ExitUpgradeableReadLock();
if (lockMode == LockMode.Write) if (_lockMode == LockMode.Write)
rwLock.ExitWriteLock(); _rwLock.ExitWriteLock();
lockMode = LockMode.None; _lockMode = LockMode.None;
} }
} }

View File

@@ -20,11 +20,8 @@ namespace System.IO
/// <returns></returns> /// <returns></returns>
public static string ReadLine(this Stream stream, Encoding encoding = null, char? eol = null) public static string ReadLine(this Stream stream, Encoding encoding = null, char? eol = null)
{ {
if (encoding == null) encoding ??= Encoding.Default;
encoding = Encoding.Default; eol ??= Environment.NewLine.Last();
if (eol == null)
eol = Environment.NewLine.Last();
if (!stream.CanRead) if (!stream.CanRead)
return null; return null;
@@ -56,11 +53,8 @@ namespace System.IO
/// <returns></returns> /// <returns></returns>
public static async Task<string> ReadLineAsync(this Stream stream, Encoding encoding = null, char? eol = null, CancellationToken cancellationToken = default) public static async Task<string> ReadLineAsync(this Stream stream, Encoding encoding = null, char? eol = null, CancellationToken cancellationToken = default)
{ {
if (encoding == null) encoding ??= Encoding.Default;
encoding = Encoding.Default; eol ??= Environment.NewLine.Last();
if (eol == null)
eol = Environment.NewLine.Last();
if (!stream.CanRead) if (!stream.CanRead)
return null; return null;

View File

@@ -177,10 +177,7 @@ namespace System
if (isValid && nameservers?.Any() == true) if (isValid && nameservers?.Any() == true)
{ {
var dnsClientType = Type.GetType("DNS.Client.DnsClient, DNS"); var dnsClientType = Type.GetType("DNS.Client.DnsClient, DNS") ?? throw new DllNotFoundException("The DNS NuGet package is required: https://www.nuget.org/packages/DNS/7.0.0");
if (dnsClientType == null)
throw new DllNotFoundException("The DNS NuGet package is required: https://www.nuget.org/packages/DNS/7.0.0");
var recordTypeType = Type.GetType("DNS.Protocol.RecordType, DNS"); var recordTypeType = Type.GetType("DNS.Protocol.RecordType, DNS");
var resolveMethodInfo = dnsClientType.GetMethod("Resolve", new[] { typeof(string), recordTypeType, typeof(CancellationToken) }); var resolveMethodInfo = dnsClientType.GetMethod("Resolve", new[] { typeof(string), recordTypeType, typeof(CancellationToken) });

View File

@@ -24,13 +24,13 @@ namespace AMWD.Common.Logging
{ {
#region Fields #region Fields
private bool isDisposed = false; private bool _isDisposed = false;
private readonly CancellationTokenSource cancellationTokenSource = new(); private readonly CancellationTokenSource _cancellationTokenSource = new();
private readonly StreamWriter fileWriter; private readonly StreamWriter _fileWriter;
private readonly Task writeTask; private readonly Task _writeTask;
private readonly AsyncQueue<QueueItem> queue = new(); private readonly AsyncQueue<QueueItem> _queue = new();
#endregion Fields #endregion Fields
@@ -45,8 +45,8 @@ namespace AMWD.Common.Logging
public FileLogger(string file, bool append = false, Encoding encoding = null) public FileLogger(string file, bool append = false, Encoding encoding = null)
{ {
FileName = file; FileName = file;
fileWriter = new StreamWriter(FileName, append, encoding ?? Encoding.UTF8); _fileWriter = new StreamWriter(FileName, append, encoding ?? Encoding.UTF8);
writeTask = Task.Run(() => WriteFileAsync(cancellationTokenSource.Token)); _writeTask = Task.Run(() => WriteFileAsync(_cancellationTokenSource.Token));
} }
/// <summary> /// <summary>
@@ -148,7 +148,7 @@ namespace AMWD.Common.Logging
/// <inheritdoc cref="ILogger.BeginScope{TState}(TState)" /> /// <inheritdoc cref="ILogger.BeginScope{TState}(TState)" />
public IDisposable BeginScope<TState>(TState state) public IDisposable BeginScope<TState>(TState state)
{ {
if (isDisposed) if (_isDisposed)
throw new ObjectDisposedException(GetType().FullName); throw new ObjectDisposedException(GetType().FullName);
return ScopeProvider?.Push(state) ?? NullScope.Instance; return ScopeProvider?.Push(state) ?? NullScope.Instance;
@@ -157,7 +157,7 @@ namespace AMWD.Common.Logging
/// <inheritdoc cref="ILogger.IsEnabled(LogLevel)" /> /// <inheritdoc cref="ILogger.IsEnabled(LogLevel)" />
public bool IsEnabled(LogLevel logLevel) public bool IsEnabled(LogLevel logLevel)
{ {
if (isDisposed) if (_isDisposed)
throw new ObjectDisposedException(GetType().FullName); throw new ObjectDisposedException(GetType().FullName);
return logLevel >= MinLevel; return logLevel >= MinLevel;
@@ -166,7 +166,7 @@ namespace AMWD.Common.Logging
/// <inheritdoc cref="ILogger.Log{TState}(LogLevel, EventId, TState, Exception, Func{TState, Exception, string})" /> /// <inheritdoc cref="ILogger.Log{TState}(LogLevel, EventId, TState, Exception, Func{TState, Exception, string})" />
public void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func<TState, Exception, string> formatter) public void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func<TState, Exception, string> formatter)
{ {
if (isDisposed) if (_isDisposed)
throw new ObjectDisposedException(GetType().FullName); throw new ObjectDisposedException(GetType().FullName);
if (!IsEnabled(logLevel)) if (!IsEnabled(logLevel))
@@ -197,13 +197,13 @@ namespace AMWD.Common.Logging
/// <inheritdoc cref="IDisposable.Dispose" /> /// <inheritdoc cref="IDisposable.Dispose" />
public void Dispose() public void Dispose()
{ {
if (!isDisposed) if (!_isDisposed)
{ {
isDisposed = true; _isDisposed = true;
cancellationTokenSource.Cancel(); _cancellationTokenSource.Cancel();
writeTask.GetAwaiter().GetResult(); _writeTask.GetAwaiter().GetResult();
fileWriter.Dispose(); _fileWriter.Dispose();
} }
} }
@@ -213,7 +213,7 @@ namespace AMWD.Common.Logging
private void WriteMessage(string name, LogLevel logLevel, int eventId, string message, Exception exception) private void WriteMessage(string name, LogLevel logLevel, int eventId, string message, Exception exception)
{ {
queue.Enqueue(new QueueItem _queue.Enqueue(new QueueItem
{ {
Timestamp = UseUtcTimestamp ? DateTime.UtcNow : DateTime.Now, Timestamp = UseUtcTimestamp ? DateTime.UtcNow : DateTime.Now,
Name = name, Name = name,
@@ -236,7 +236,7 @@ namespace AMWD.Common.Logging
QueueItem[] items; QueueItem[] items;
try try
{ {
items = await queue.DequeueAvailableAsync(cancellationToken: token); items = await _queue.DequeueAvailableAsync(cancellationToken: token).ConfigureAwait(false);
} }
catch (OperationCanceledException) catch (OperationCanceledException)
{ {
@@ -302,10 +302,10 @@ namespace AMWD.Common.Logging
sb.Append(message.Replace("\n", "\n" + timestampPadding + logLevelPadding)); sb.Append(message.Replace("\n", "\n" + timestampPadding + logLevelPadding));
await fileWriter.WriteLineAsync(sb.ToString()); await _fileWriter.WriteLineAsync(sb.ToString()).ConfigureAwait(false);
} }
await fileWriter.FlushAsync(); await _fileWriter.FlushAsync().ConfigureAwait(false);
} }
} }

View File

@@ -12,10 +12,10 @@ namespace System.Collections.Generic
{ {
#region Fields #region Fields
private readonly Queue<T> queue; private readonly Queue<T> _queue;
private TaskCompletionSource<bool> dequeueTcs = new(); private TaskCompletionSource<bool> _dequeueTcs = new();
private TaskCompletionSource<bool> availableTcs = new(); private TaskCompletionSource<bool> _availableTcs = new();
#endregion Fields #endregion Fields
@@ -27,7 +27,7 @@ namespace System.Collections.Generic
[ExcludeFromCodeCoverage] [ExcludeFromCodeCoverage]
public AsyncQueue() public AsyncQueue()
{ {
queue = new Queue<T>(); _queue = new Queue<T>();
} }
/// <summary> /// <summary>
@@ -40,7 +40,7 @@ namespace System.Collections.Generic
[ExcludeFromCodeCoverage] [ExcludeFromCodeCoverage]
public AsyncQueue(IEnumerable<T> collection) public AsyncQueue(IEnumerable<T> collection)
{ {
queue = new Queue<T>(); _queue = new Queue<T>();
Enqueue(collection); Enqueue(collection);
} }
@@ -52,7 +52,7 @@ namespace System.Collections.Generic
[ExcludeFromCodeCoverage] [ExcludeFromCodeCoverage]
public AsyncQueue(int capacity) public AsyncQueue(int capacity)
{ {
queue = new Queue<T>(capacity); _queue = new Queue<T>(capacity);
} }
#endregion Constructors #endregion Constructors
@@ -67,9 +67,9 @@ namespace System.Collections.Generic
{ {
get get
{ {
lock (queue) lock (_queue)
{ {
return queue.Count; return _queue.Count;
} }
} }
} }
@@ -84,9 +84,9 @@ namespace System.Collections.Generic
[ExcludeFromCodeCoverage] [ExcludeFromCodeCoverage]
public void Clear() public void Clear()
{ {
lock (queue) lock (_queue)
{ {
queue.Clear(); _queue.Clear();
} }
} }
@@ -98,9 +98,9 @@ namespace System.Collections.Generic
[ExcludeFromCodeCoverage] [ExcludeFromCodeCoverage]
public bool Contains(T item) public bool Contains(T item)
{ {
lock (queue) lock (_queue)
{ {
return queue.Contains(item); return _queue.Contains(item);
} }
} }
@@ -115,9 +115,9 @@ namespace System.Collections.Generic
[ExcludeFromCodeCoverage] [ExcludeFromCodeCoverage]
public void CopyTo(T[] array, int arrayIndex) public void CopyTo(T[] array, int arrayIndex)
{ {
lock (queue) lock (_queue)
{ {
queue.CopyTo(array, arrayIndex); _queue.CopyTo(array, arrayIndex);
} }
} }
@@ -129,9 +129,9 @@ namespace System.Collections.Generic
[ExcludeFromCodeCoverage] [ExcludeFromCodeCoverage]
public T Dequeue() public T Dequeue()
{ {
lock (queue) lock (_queue)
{ {
return queue.Dequeue(); return _queue.Dequeue();
} }
} }
@@ -141,11 +141,11 @@ namespace System.Collections.Generic
/// <param name="item">The object to add to the <see cref="AsyncQueue{T}"/>. The value can be null for reference types.</param> /// <param name="item">The object to add to the <see cref="AsyncQueue{T}"/>. The value can be null for reference types.</param>
public void Enqueue(T item) public void Enqueue(T item)
{ {
lock (queue) lock (_queue)
{ {
queue.Enqueue(item); _queue.Enqueue(item);
SetToken(dequeueTcs); SetToken(_dequeueTcs);
SetToken(availableTcs); SetToken(_availableTcs);
} }
} }
@@ -157,9 +157,9 @@ namespace System.Collections.Generic
[ExcludeFromCodeCoverage] [ExcludeFromCodeCoverage]
public T Peek() public T Peek()
{ {
lock (queue) lock (_queue)
{ {
return queue.Peek(); return _queue.Peek();
} }
} }
@@ -170,9 +170,9 @@ namespace System.Collections.Generic
[ExcludeFromCodeCoverage] [ExcludeFromCodeCoverage]
public T[] ToArray() public T[] ToArray()
{ {
lock (queue) lock (_queue)
{ {
return queue.ToArray(); return _queue.ToArray();
} }
} }
@@ -182,9 +182,9 @@ namespace System.Collections.Generic
[ExcludeFromCodeCoverage] [ExcludeFromCodeCoverage]
public void TrimExcess() public void TrimExcess()
{ {
lock (queue) lock (_queue)
{ {
queue.TrimExcess(); _queue.TrimExcess();
} }
} }
@@ -203,21 +203,21 @@ namespace System.Collections.Generic
while (true) while (true)
{ {
TaskCompletionSource<bool> internalDequeueTcs; TaskCompletionSource<bool> internalDequeueTcs;
lock (queue) lock (_queue)
{ {
if (queue.Count > 0) if (_queue.Count > 0)
{ {
int count = queue.Count; int count = _queue.Count;
if (maxCount > 0 && count > maxCount) if (maxCount > 0 && count > maxCount)
count = maxCount; count = maxCount;
var items = new T[count]; var items = new T[count];
for (int i = 0; i < count; i++) for (int i = 0; i < count; i++)
items[i] = queue.Dequeue(); items[i] = _queue.Dequeue();
return items; return items;
} }
internalDequeueTcs = ResetToken(ref dequeueTcs); internalDequeueTcs = ResetToken(ref _dequeueTcs);
} }
await WaitAsync(internalDequeueTcs, cancellationToken).ConfigureAwait(false); await WaitAsync(internalDequeueTcs, cancellationToken).ConfigureAwait(false);
@@ -238,17 +238,17 @@ namespace System.Collections.Generic
while (true) while (true)
{ {
TaskCompletionSource<bool> internalDequeueTcs; TaskCompletionSource<bool> internalDequeueTcs;
lock (queue) lock (_queue)
{ {
if (count <= queue.Count) if (count <= _queue.Count)
{ {
var items = new T[count]; var items = new T[count];
for (int i = 0; i < count; i++) for (int i = 0; i < count; i++)
items[i] = queue.Dequeue(); items[i] = _queue.Dequeue();
return items; return items;
} }
internalDequeueTcs = ResetToken(ref dequeueTcs); internalDequeueTcs = ResetToken(ref _dequeueTcs);
} }
await WaitAsync(internalDequeueTcs, cancellationToken).ConfigureAwait(false); await WaitAsync(internalDequeueTcs, cancellationToken).ConfigureAwait(false);
@@ -265,12 +265,12 @@ namespace System.Collections.Generic
while (true) while (true)
{ {
TaskCompletionSource<bool> internalDequeueTcs; TaskCompletionSource<bool> internalDequeueTcs;
lock (queue) lock (_queue)
{ {
if (queue.Count > 0) if (_queue.Count > 0)
return queue.Dequeue(); return _queue.Dequeue();
internalDequeueTcs = ResetToken(ref dequeueTcs); internalDequeueTcs = ResetToken(ref _dequeueTcs);
} }
await WaitAsync(internalDequeueTcs, cancellationToken).ConfigureAwait(false); await WaitAsync(internalDequeueTcs, cancellationToken).ConfigureAwait(false);
@@ -285,12 +285,12 @@ namespace System.Collections.Generic
public async Task WaitAsync(CancellationToken cancellationToken = default) public async Task WaitAsync(CancellationToken cancellationToken = default)
{ {
TaskCompletionSource<bool> internalAvailableTcs; TaskCompletionSource<bool> internalAvailableTcs;
lock (queue) lock (_queue)
{ {
if (queue.Count > 0) if (_queue.Count > 0)
return; return;
internalAvailableTcs = ResetToken(ref availableTcs); internalAvailableTcs = ResetToken(ref _availableTcs);
} }
await WaitAsync(internalAvailableTcs, cancellationToken).ConfigureAwait(false); await WaitAsync(internalAvailableTcs, cancellationToken).ConfigureAwait(false);
@@ -347,10 +347,10 @@ namespace System.Collections.Generic
/// <returns>true if item is successfully removed; otherwise, false. This method also returns false if item was not found in the <see cref="AsyncQueue{T}"/>.</returns> /// <returns>true if item is successfully removed; otherwise, false. This method also returns false if item was not found in the <see cref="AsyncQueue{T}"/>.</returns>
public bool Remove(T item) public bool Remove(T item)
{ {
lock (queue) lock (_queue)
{ {
var copy = new Queue<T>(queue); var copy = new Queue<T>(_queue);
queue.Clear(); _queue.Clear();
bool found = false; bool found = false;
int count = copy.Count; int count = copy.Count;
@@ -359,7 +359,7 @@ namespace System.Collections.Generic
var element = copy.Dequeue(); var element = copy.Dequeue();
if (found) if (found)
{ {
queue.Enqueue(element); _queue.Enqueue(element);
continue; continue;
} }
@@ -369,7 +369,7 @@ namespace System.Collections.Generic
continue; continue;
} }
queue.Enqueue(element); _queue.Enqueue(element);
} }
return found; return found;
@@ -382,19 +382,19 @@ namespace System.Collections.Generic
/// <param name="collection">The objects to add to the <see cref="AsyncQueue{T}"/>.</param> /// <param name="collection">The objects to add to the <see cref="AsyncQueue{T}"/>.</param>
public void Enqueue(IEnumerable<T> collection) public void Enqueue(IEnumerable<T> collection)
{ {
lock (queue) lock (_queue)
{ {
bool hasElements = false; bool hasElements = false;
foreach (var element in collection) foreach (var element in collection)
{ {
hasElements = true; hasElements = true;
queue.Enqueue(element); _queue.Enqueue(element);
} }
if (hasElements) if (hasElements)
{ {
SetToken(dequeueTcs); SetToken(_dequeueTcs);
SetToken(availableTcs); SetToken(_availableTcs);
} }
} }
} }
@@ -425,7 +425,7 @@ namespace System.Collections.Generic
{ {
if (await Task.WhenAny(tcs.Task, Task.Delay(-1, cancellationToken)) == tcs.Task) if (await Task.WhenAny(tcs.Task, Task.Delay(-1, cancellationToken)) == tcs.Task)
{ {
await tcs.Task; await tcs.Task.ConfigureAwait(false);
return; return;
} }

View File

@@ -8,9 +8,12 @@ namespace System.Security.Cryptography
/// </summary> /// </summary>
public class CryptographyHelper public class CryptographyHelper
{ {
private static int saltLength = 8; // "readonly" not added due to UnitTests
#pragma warning disable IDE0044 // Add "readonly" modifier
private static int _saltLength = 8;
#pragma warning restore IDE0044 // Add "readonly" modifier
private readonly string masterKeyFile; private readonly string _masterKeyFile;
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="CryptographyHelper"/> class. /// Initializes a new instance of the <see cref="CryptographyHelper"/> class.
@@ -18,17 +21,17 @@ namespace System.Security.Cryptography
/// <param name="keyFile">The (absolute) path to the crypto key file. On <c>null</c> the file 'crypto.key' at the executing assembly location will be used.</param> /// <param name="keyFile">The (absolute) path to the crypto key file. On <c>null</c> the file 'crypto.key' at the executing assembly location will be used.</param>
public CryptographyHelper(string keyFile = null) public CryptographyHelper(string keyFile = null)
{ {
masterKeyFile = keyFile; _masterKeyFile = keyFile;
if (string.IsNullOrWhiteSpace(masterKeyFile)) if (string.IsNullOrWhiteSpace(_masterKeyFile))
masterKeyFile = "crypto.key"; _masterKeyFile = "crypto.key";
if (!Path.IsPathRooted(masterKeyFile)) if (!Path.IsPathRooted(_masterKeyFile))
masterKeyFile = Path.Combine(AppContext.BaseDirectory, masterKeyFile); _masterKeyFile = Path.Combine(AppContext.BaseDirectory, _masterKeyFile);
string pw = File.Exists(masterKeyFile) ? File.ReadAllText(masterKeyFile) : null; string pw = File.Exists(_masterKeyFile) ? File.ReadAllText(_masterKeyFile) : null;
if (string.IsNullOrWhiteSpace(pw)) if (string.IsNullOrWhiteSpace(pw))
File.WriteAllText(masterKeyFile, GetRandomString(64)); File.WriteAllText(_masterKeyFile, GetRandomString(64));
} }
#region Instance methods #region Instance methods
@@ -46,8 +49,7 @@ namespace System.Security.Cryptography
/// <returns>The decrypted data.</returns> /// <returns>The decrypted data.</returns>
public byte[] DecryptAes(byte[] cipher, string password = null) public byte[] DecryptAes(byte[] cipher, string password = null)
{ {
if (password == null) password ??= File.ReadAllText(_masterKeyFile);
password = File.ReadAllText(masterKeyFile);
return AesDecrypt(cipher, password); return AesDecrypt(cipher, password);
} }
@@ -63,8 +65,7 @@ namespace System.Security.Cryptography
/// <returns>The encrypted data (cipher).</returns> /// <returns>The encrypted data (cipher).</returns>
public byte[] EncryptAes(byte[] plain, string password = null) public byte[] EncryptAes(byte[] plain, string password = null)
{ {
if (password == null) password ??= File.ReadAllText(_masterKeyFile);
password = File.ReadAllText(masterKeyFile);
return AesEncrypt(plain, password); return AesEncrypt(plain, password);
} }
@@ -110,8 +111,7 @@ namespace System.Security.Cryptography
/// <returns>The decrypted data.</returns> /// <returns>The decrypted data.</returns>
public byte[] DecryptTripleDes(byte[] cipher, string password = null) public byte[] DecryptTripleDes(byte[] cipher, string password = null)
{ {
if (password == null) password ??= File.ReadAllText(_masterKeyFile);
password = File.ReadAllText(masterKeyFile);
return TripleDesDecrypt(cipher, password); return TripleDesDecrypt(cipher, password);
} }
@@ -127,8 +127,7 @@ namespace System.Security.Cryptography
/// <returns>The encrypted data (cipher).</returns> /// <returns>The encrypted data (cipher).</returns>
public byte[] EncryptTripleDes(byte[] plain, string password = null) public byte[] EncryptTripleDes(byte[] plain, string password = null)
{ {
if (password == null) password ??= File.ReadAllText(_masterKeyFile);
password = File.ReadAllText(masterKeyFile);
return TripleDesEncrypt(plain, password); return TripleDesEncrypt(plain, password);
} }
@@ -183,8 +182,8 @@ namespace System.Security.Cryptography
/// <returns>The decrypted data.</returns> /// <returns>The decrypted data.</returns>
public static byte[] AesDecrypt(byte[] cipher, string password) public static byte[] AesDecrypt(byte[] cipher, string password)
{ {
byte[] salt = new byte[saltLength]; byte[] salt = new byte[_saltLength];
Array.Copy(cipher, salt, saltLength); Array.Copy(cipher, salt, _saltLength);
using var gen = new Rfc2898DeriveBytes(password, salt); using var gen = new Rfc2898DeriveBytes(password, salt);
using var aes = Aes.Create(); using var aes = Aes.Create();
@@ -197,7 +196,7 @@ namespace System.Security.Cryptography
using var ms = new MemoryStream(); using var ms = new MemoryStream();
using var cs = new CryptoStream(ms, aes.CreateDecryptor(), CryptoStreamMode.Write); using var cs = new CryptoStream(ms, aes.CreateDecryptor(), CryptoStreamMode.Write);
cs.Write(cipher, saltLength, cipher.Length - saltLength); cs.Write(cipher, _saltLength, cipher.Length - _saltLength);
cs.FlushFinalBlock(); cs.FlushFinalBlock();
return ms.ToArray(); return ms.ToArray();
@@ -224,7 +223,7 @@ namespace System.Security.Cryptography
/// <returns>The encrypted data (cipher).</returns> /// <returns>The encrypted data (cipher).</returns>
public static byte[] AesEncrypt(byte[] plain, string password) public static byte[] AesEncrypt(byte[] plain, string password)
{ {
byte[] salt = GetRandomBytes(saltLength); byte[] salt = GetRandomBytes(_saltLength);
using var gen = new Rfc2898DeriveBytes(password, salt); using var gen = new Rfc2898DeriveBytes(password, salt);
using var aes = Aes.Create(); using var aes = Aes.Create();
@@ -269,8 +268,8 @@ namespace System.Security.Cryptography
/// <returns>The decrypted data.</returns> /// <returns>The decrypted data.</returns>
public static byte[] TripleDesDecrypt(byte[] cipher, string password) public static byte[] TripleDesDecrypt(byte[] cipher, string password)
{ {
byte[] salt = new byte[saltLength]; byte[] salt = new byte[_saltLength];
Array.Copy(cipher, salt, saltLength); Array.Copy(cipher, salt, _saltLength);
using var gen = new Rfc2898DeriveBytes(password, salt); using var gen = new Rfc2898DeriveBytes(password, salt);
using var tdes = TripleDES.Create(); using var tdes = TripleDES.Create();
@@ -283,7 +282,7 @@ namespace System.Security.Cryptography
using var ms = new MemoryStream(); using var ms = new MemoryStream();
using var cs = new CryptoStream(ms, tdes.CreateDecryptor(), CryptoStreamMode.Write); using var cs = new CryptoStream(ms, tdes.CreateDecryptor(), CryptoStreamMode.Write);
cs.Write(cipher, saltLength, cipher.Length - saltLength); cs.Write(cipher, _saltLength, cipher.Length - _saltLength);
cs.FlushFinalBlock(); cs.FlushFinalBlock();
return ms.ToArray(); return ms.ToArray();
@@ -297,7 +296,7 @@ namespace System.Security.Cryptography
/// <returns>The encrypted data (cipher).</returns> /// <returns>The encrypted data (cipher).</returns>
public static byte[] TripleDesEncrypt(byte[] plain, string password) public static byte[] TripleDesEncrypt(byte[] plain, string password)
{ {
byte[] salt = GetRandomBytes(saltLength); byte[] salt = GetRandomBytes(_saltLength);
using var gen = new Rfc2898DeriveBytes(password, salt); using var gen = new Rfc2898DeriveBytes(password, salt);
using var tdes = TripleDES.Create(); using var tdes = TripleDES.Create();

View File

@@ -15,9 +15,9 @@ namespace AMWD.Common.Utilities
{ {
#region Data #region Data
private Timer timer; private Timer _timer;
private bool nextRunPending; private bool _nextRunPending;
/// <summary> /// <summary>
/// The synchronisation object. /// The synchronisation object.
@@ -130,13 +130,13 @@ namespace AMWD.Common.Utilities
tcs = CreateTcs(); tcs = CreateTcs();
} }
IsWaitingToRun = true; IsWaitingToRun = true;
if (timer != null) if (_timer != null)
{ {
timer.Change(Delay, Timeout.InfiniteTimeSpan); _timer.Change(Delay, Timeout.InfiniteTimeSpan);
} }
else else
{ {
timer = new Timer(OnTimerCallback, null, Delay, Timeout.InfiniteTimeSpan); _timer = new Timer(OnTimerCallback, null, Delay, Timeout.InfiniteTimeSpan);
} }
} }
} }
@@ -152,9 +152,9 @@ namespace AMWD.Common.Utilities
lock (syncLock) lock (syncLock)
{ {
IsWaitingToRun = false; IsWaitingToRun = false;
nextRunPending = false; _nextRunPending = false;
timer?.Dispose(); _timer?.Dispose();
timer = null; _timer = null;
if (!IsRunning) if (!IsRunning)
{ {
localTcs = tcs; localTcs = tcs;
@@ -179,13 +179,13 @@ namespace AMWD.Common.Utilities
return false; return false;
} }
IsWaitingToRun = true; IsWaitingToRun = true;
if (timer != null) if (_timer != null)
{ {
timer.Change(TimeSpan.Zero, Timeout.InfiniteTimeSpan); _timer.Change(TimeSpan.Zero, Timeout.InfiniteTimeSpan);
} }
else else
{ {
timer = new Timer(OnTimerCallback, null, TimeSpan.Zero, Timeout.InfiniteTimeSpan); _timer = new Timer(OnTimerCallback, null, TimeSpan.Zero, Timeout.InfiniteTimeSpan);
} }
return true; return true;
} }
@@ -256,7 +256,7 @@ namespace AMWD.Common.Utilities
{ {
tcs = CreateTcs(); tcs = CreateTcs();
IsWaitingToRun = true; IsWaitingToRun = true;
timer = new Timer(OnTimerCallback, null, Delay, Timeout.InfiniteTimeSpan); _timer = new Timer(OnTimerCallback, null, Delay, Timeout.InfiniteTimeSpan);
return this; return this;
} }
@@ -285,7 +285,7 @@ namespace AMWD.Common.Utilities
if (IsRunning) if (IsRunning)
{ {
// Currently running, remember and do nothing for now // Currently running, remember and do nothing for now
nextRunPending = true; _nextRunPending = true;
return; return;
} }
IsRunning = true; IsRunning = true;
@@ -308,12 +308,12 @@ namespace AMWD.Common.Utilities
{ {
runAgain = false; runAgain = false;
IsRunning = false; IsRunning = false;
nextRunPending = false; _nextRunPending = false;
localTcs = tcs; localTcs = tcs;
if (!IsWaitingToRun) if (!IsWaitingToRun)
{ {
timer?.Dispose(); _timer?.Dispose();
timer = null; _timer = null;
} }
} }
exceptionHandler?.Invoke(ex); exceptionHandler?.Invoke(ex);
@@ -322,16 +322,16 @@ namespace AMWD.Common.Utilities
{ {
lock (syncLock) lock (syncLock)
{ {
runAgain = nextRunPending; runAgain = _nextRunPending;
IsRunning = runAgain; IsRunning = runAgain;
nextRunPending = false; _nextRunPending = false;
if (!runAgain) if (!runAgain)
{ {
if (!IsWaitingToRun) if (!IsWaitingToRun)
{ {
localTcs = tcs; localTcs = tcs;
timer?.Dispose(); _timer?.Dispose();
timer = null; _timer = null;
} }
} }
} }
@@ -403,19 +403,19 @@ namespace AMWD.Common.Utilities
/// <typeparam name="TResult">The type of the result value.</typeparam> /// <typeparam name="TResult">The type of the result value.</typeparam>
protected class TaskCompletionSourceWrapper<TResult> : TaskCompletionSourceWrapper protected class TaskCompletionSourceWrapper<TResult> : TaskCompletionSourceWrapper
{ {
private readonly TaskCompletionSource<TResult> tcs; private readonly TaskCompletionSource<TResult> _tcs;
/// <summary> /// <summary>
/// Gets the <see cref="Task{TResult}"/> of the <see cref="TaskCompletionSource{TResult}"/>. /// Gets the <see cref="Task{TResult}"/> of the <see cref="TaskCompletionSource{TResult}"/>.
/// </summary> /// </summary>
public override Task Task => tcs.Task; public override Task Task => _tcs.Task;
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="TaskCompletionSourceWrapper{TResult}"/> class. /// Initializes a new instance of the <see cref="TaskCompletionSourceWrapper{TResult}"/> class.
/// </summary> /// </summary>
public TaskCompletionSourceWrapper() public TaskCompletionSourceWrapper()
{ {
tcs = new TaskCompletionSource<TResult>(); _tcs = new TaskCompletionSource<TResult>();
} }
/// <summary> /// <summary>
@@ -424,13 +424,13 @@ namespace AMWD.Common.Utilities
/// </summary> /// </summary>
/// <param name="result">The result value to bind to this <see cref="Task{TResult}"/>.</param> /// <param name="result">The result value to bind to this <see cref="Task{TResult}"/>.</param>
/// <seealso cref="TaskCompletionSource{TResult}.TrySetResult(TResult)"/> /// <seealso cref="TaskCompletionSource{TResult}.TrySetResult(TResult)"/>
public void TrySetResult(TResult result) => tcs.TrySetResult(result); public void TrySetResult(TResult result) => _tcs.TrySetResult(result);
/// <inheritdoc/> /// <inheritdoc/>
public override void TrySetException(Exception exception) => tcs.TrySetException(exception); public override void TrySetException(Exception exception) => _tcs.TrySetException(exception);
/// <inheritdoc/> /// <inheritdoc/>
public override void TrySetCanceled() => tcs.TrySetCanceled(); public override void TrySetCanceled() => _tcs.TrySetCanceled();
} }
#endregion Internal TaskCompletionSourceWrapper classes #endregion Internal TaskCompletionSourceWrapper classes

View File

@@ -150,7 +150,7 @@ namespace AMWD.Common.Utilities
if (addressFamily != AddressFamily.Unspecified && ipAddress.AddressFamily != addressFamily) if (addressFamily != AddressFamily.Unspecified && ipAddress.AddressFamily != addressFamily)
return null; return null;
return ipAddress; return ipAddress.IsIPv4MappedToIPv6 ? ipAddress.MapToIPv4() : ipAddress;
} }
return null; return null;

View File

@@ -5,7 +5,14 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [Upcoming](https://git.am-wd.de/AM.WD/common/compare/v1.11.0...main) - 0000-00-00 ## Upcoming - 0000-00-00
###### Diffs
- [AMWD.Common](https://git.am-wd.de/AM.WD/common/compare/v2.0.1...main)
- [AMWD.Common.AspNetCore](https://git.am-wd.de/AM.WD/common/compare/asp/v3.0.0...main)
- [AMWD.Common.EntityFrameworkCore](https://git.am-wd.de/AM.WD/common/compare/efc/v3.0.0...main)
- [AMWD.Common.Test](https://git.am-wd.de/AM.WD/common/compare/test/v2.1.1...main)
### Added ### Added
@@ -13,6 +20,121 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- `TarReader` and `TarWriter` for TAR archives - `TarReader` and `TarWriter` for TAR archives
## asp/v3.0.0, efc/v3.0.0 - 2023-12-28
###### Diffs
- [AMWD.Common.AspNetCore](https://git.am-wd.de/AM.WD/common/compare/asp/v2.2.0...asp/v3.0.0)
- [AMWD.Common.EntityFrameworkCore](https://git.am-wd.de/AM.WD/common/compare/efc/v2.0.1...efc/v3.0.0)
### Added
- Support for .NET 8.0 LTS
### Changed
- Renamed all private members to start with an underscore
- Split "Add `DateOnly` and `TimeOnly` converters" into two extension methods
## asp/v2.2.0 - 2023-12-05
###### Diffs
- [AMWD.Common.AspNetCore](https://git.am-wd.de/AM.WD/common/compare/asp/v2.1.0...asp/v2.2.0)
### Changed
- Using `AddHostedService<>()` with "implementation factory" for `AddSingletonHostedService<>()` to resove singleton instance
- `GetRemoteIpAddress()` converts a mixed IPv4 address on IPv6 (e.g. `::ffff:127.0.0.1`) to a clean IPv4 address (e.g. `127.0.0.1`)
### Removed
- Removed `BackgroundServiceStarter`
## v2.0.1, asp/v2.1.0, efc/v2.0.1, test/v2.1.1 - 2023-11-23
###### Diffs
- [AMWD.Common](https://git.am-wd.de/AM.WD/common/compare/v2.0.0...v2.0.1)
- [AMWD.Common.AspNetCore](https://git.am-wd.de/AM.WD/common/compare/asp/v2.0.0...asp/v2.1.0)
- [AMWD.Common.EntityFrameworkCore](https://git.am-wd.de/AM.WD/common/compare/efc/v2.0.0...efc/v2.0.1)
- [AMWD.Common.Test](https://git.am-wd.de/AM.WD/common/compare/test/v2.1.0...test/v2.1.1)
### Added
- Added `ConfigureAwait(false)` to async calls where appropriate
- Added localhost and private IPv6 subnet to `UseProxyHosting()`
### Changed
- Changed order of header evaluation for remote IP address parsing: `Cf-Connecting-Ip`, `X-Real-IP`, `X-Forwarded-For`
### Fixed
- `GetRemoteIpAddress()` is able to get address when multiple proxies are used
## test/v2.1.0 - 2023-10-13
###### Diffs
- [AMWD.Common.Test](https://git.am-wd.de/AM.WD/common/compare/test/v2.0.0...test/v2.1.0)
### Added
- Added `SnapshotAssert` for better UnitTesting
### Changed
- Moved `AMWD.Common.Moq` to `AMWD.Common.Test`
## [v2.0.0](https://git.am-wd.de/AM.WD/common/compare/v1.13.0...v2.0.0) - 2023-08-08
### Added
- `CommandLineParser` as alternative to the `ConfigurationBuilder.AddCommandLine` from Microsoft
### Removed
- Support for .NET Core 3.1
## [v1.13.0](https://git.am-wd.de/AM.WD/common/compare/v1.12.0...v1.13.0) - 2023-06-27
### Added
- `ProtectedPathMiddleware` to secure even static file paths
### Changed
- Moved `BasicAuthentication`* into sub-namespace `Security`
## [v1.12.0](https://git.am-wd.de/AM.WD/common/compare/v1.11.1...v1.12.0) - 2023-06-01
### Changed
- Renamed `IPBlacklistAttribute` to `IPBlockListAttribute`
- Renamed `IPWhitelistAttribute` to `IPAllowListAttribute`
- `HttpContextExtensions`
- `GetAntiforgeryToken()` now returns the header name also
- `GetRemoteIpAddress()` checks following additional headers by default:
- `X-Forwarded-For`
- `X-Real-IP`
- `CF-Connecting-IP`
## [v1.11.1](https://git.am-wd.de/AM.WD/common/compare/v1.11.0...v1.11.1) - 2023-05-11
### Fixed
- `JsonExtensions.GetValue<T>()` now is case insensitive and detects the correct property name of a `JObject`.
## [v1.11.0](https://git.am-wd.de/AM.WD/common/compare/v1.10.0...v1.11.0) - 2023-03-29 ## [v1.11.0](https://git.am-wd.de/AM.WD/common/compare/v1.10.0...v1.11.0) - 2023-03-29
### Added ### Added

View File

@@ -15,7 +15,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{F2C7556A-99E
EndProject EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "test", "test", "{E5DF156A-6C8B-4004-BA4C-A8DDE6FD3ECD}" Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "test", "test", "{E5DF156A-6C8B-4004-BA4C-A8DDE6FD3ECD}"
EndProject EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AMWD.Common.Moq", "AMWD.Common.Moq\AMWD.Common.Moq.csproj", "{6EBA2792-0B66-4C90-89A1-4E1D26D16443}" Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AMWD.Common.Test", "AMWD.Common.Test\AMWD.Common.Test.csproj", "{6EBA2792-0B66-4C90-89A1-4E1D26D16443}"
EndProject EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "UnitTests", "UnitTests\UnitTests.csproj", "{9469D87B-126E-4338-92E3-701F762CB54D}" Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "UnitTests", "UnitTests\UnitTests.csproj", "{9469D87B-126E-4338-92E3-701F762CB54D}"
EndProject EndProject
@@ -31,7 +31,6 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Build", "Build", "{7196DA2B
ProjectSection(SolutionItems) = preProject ProjectSection(SolutionItems) = preProject
.gitlab-ci.yml = .gitlab-ci.yml .gitlab-ci.yml = .gitlab-ci.yml
directory.build.props = directory.build.props directory.build.props = directory.build.props
directory.build.targets = directory.build.targets
EndProjectSection EndProjectSection
EndProject EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Utils", "Utils", "{93EC8B16-7DEF-4E39-B590-E804DEF7C607}" Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Utils", "Utils", "{93EC8B16-7DEF-4E39-B590-E804DEF7C607}"
@@ -39,43 +38,33 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Utils", "Utils", "{93EC8B16
.editorconfig = .editorconfig .editorconfig = .editorconfig
.gitignore = .gitignore .gitignore = .gitignore
CodeMaid.config = CodeMaid.config CodeMaid.config = CodeMaid.config
nuget.config = nuget.config
EndProjectSection EndProjectSection
EndProject EndProject
Global Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU Debug|Any CPU = Debug|Any CPU
DebugLocal|Any CPU = DebugLocal|Any CPU
Release|Any CPU = Release|Any CPU Release|Any CPU = Release|Any CPU
EndGlobalSection EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution GlobalSection(ProjectConfigurationPlatforms) = postSolution
{F512C474-B670-4E47-911E-7C0674AA8E7E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {F512C474-B670-4E47-911E-7C0674AA8E7E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{F512C474-B670-4E47-911E-7C0674AA8E7E}.Debug|Any CPU.Build.0 = Debug|Any CPU {F512C474-B670-4E47-911E-7C0674AA8E7E}.Debug|Any CPU.Build.0 = Debug|Any CPU
{F512C474-B670-4E47-911E-7C0674AA8E7E}.DebugLocal|Any CPU.ActiveCfg = DebugLocal|Any CPU
{F512C474-B670-4E47-911E-7C0674AA8E7E}.DebugLocal|Any CPU.Build.0 = DebugLocal|Any CPU
{F512C474-B670-4E47-911E-7C0674AA8E7E}.Release|Any CPU.ActiveCfg = Release|Any CPU {F512C474-B670-4E47-911E-7C0674AA8E7E}.Release|Any CPU.ActiveCfg = Release|Any CPU
{F512C474-B670-4E47-911E-7C0674AA8E7E}.Release|Any CPU.Build.0 = Release|Any CPU {F512C474-B670-4E47-911E-7C0674AA8E7E}.Release|Any CPU.Build.0 = Release|Any CPU
{725F40C9-8172-487F-B3D0-D7E38B4DB197}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {725F40C9-8172-487F-B3D0-D7E38B4DB197}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{725F40C9-8172-487F-B3D0-D7E38B4DB197}.Debug|Any CPU.Build.0 = Debug|Any CPU {725F40C9-8172-487F-B3D0-D7E38B4DB197}.Debug|Any CPU.Build.0 = Debug|Any CPU
{725F40C9-8172-487F-B3D0-D7E38B4DB197}.DebugLocal|Any CPU.ActiveCfg = DebugLocal|Any CPU
{725F40C9-8172-487F-B3D0-D7E38B4DB197}.DebugLocal|Any CPU.Build.0 = DebugLocal|Any CPU
{725F40C9-8172-487F-B3D0-D7E38B4DB197}.Release|Any CPU.ActiveCfg = Release|Any CPU {725F40C9-8172-487F-B3D0-D7E38B4DB197}.Release|Any CPU.ActiveCfg = Release|Any CPU
{725F40C9-8172-487F-B3D0-D7E38B4DB197}.Release|Any CPU.Build.0 = Release|Any CPU {725F40C9-8172-487F-B3D0-D7E38B4DB197}.Release|Any CPU.Build.0 = Release|Any CPU
{7091CECF-C981-4FB9-9CC6-91C4E65A6356}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {7091CECF-C981-4FB9-9CC6-91C4E65A6356}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{7091CECF-C981-4FB9-9CC6-91C4E65A6356}.Debug|Any CPU.Build.0 = Debug|Any CPU {7091CECF-C981-4FB9-9CC6-91C4E65A6356}.Debug|Any CPU.Build.0 = Debug|Any CPU
{7091CECF-C981-4FB9-9CC6-91C4E65A6356}.DebugLocal|Any CPU.ActiveCfg = DebugLocal|Any CPU
{7091CECF-C981-4FB9-9CC6-91C4E65A6356}.DebugLocal|Any CPU.Build.0 = DebugLocal|Any CPU
{7091CECF-C981-4FB9-9CC6-91C4E65A6356}.Release|Any CPU.ActiveCfg = Release|Any CPU {7091CECF-C981-4FB9-9CC6-91C4E65A6356}.Release|Any CPU.ActiveCfg = Release|Any CPU
{7091CECF-C981-4FB9-9CC6-91C4E65A6356}.Release|Any CPU.Build.0 = Release|Any CPU {7091CECF-C981-4FB9-9CC6-91C4E65A6356}.Release|Any CPU.Build.0 = Release|Any CPU
{6EBA2792-0B66-4C90-89A1-4E1D26D16443}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {6EBA2792-0B66-4C90-89A1-4E1D26D16443}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{6EBA2792-0B66-4C90-89A1-4E1D26D16443}.Debug|Any CPU.Build.0 = Debug|Any CPU {6EBA2792-0B66-4C90-89A1-4E1D26D16443}.Debug|Any CPU.Build.0 = Debug|Any CPU
{6EBA2792-0B66-4C90-89A1-4E1D26D16443}.DebugLocal|Any CPU.ActiveCfg = DebugLocal|Any CPU
{6EBA2792-0B66-4C90-89A1-4E1D26D16443}.DebugLocal|Any CPU.Build.0 = DebugLocal|Any CPU
{6EBA2792-0B66-4C90-89A1-4E1D26D16443}.Release|Any CPU.ActiveCfg = Release|Any CPU {6EBA2792-0B66-4C90-89A1-4E1D26D16443}.Release|Any CPU.ActiveCfg = Release|Any CPU
{6EBA2792-0B66-4C90-89A1-4E1D26D16443}.Release|Any CPU.Build.0 = Release|Any CPU {6EBA2792-0B66-4C90-89A1-4E1D26D16443}.Release|Any CPU.Build.0 = Release|Any CPU
{9469D87B-126E-4338-92E3-701F762CB54D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {9469D87B-126E-4338-92E3-701F762CB54D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{9469D87B-126E-4338-92E3-701F762CB54D}.Debug|Any CPU.Build.0 = Debug|Any CPU {9469D87B-126E-4338-92E3-701F762CB54D}.Debug|Any CPU.Build.0 = Debug|Any CPU
{9469D87B-126E-4338-92E3-701F762CB54D}.DebugLocal|Any CPU.ActiveCfg = DebugLocal|Any CPU
{9469D87B-126E-4338-92E3-701F762CB54D}.DebugLocal|Any CPU.Build.0 = DebugLocal|Any CPU
{9469D87B-126E-4338-92E3-701F762CB54D}.Release|Any CPU.ActiveCfg = Release|Any CPU {9469D87B-126E-4338-92E3-701F762CB54D}.Release|Any CPU.ActiveCfg = Release|Any CPU
{9469D87B-126E-4338-92E3-701F762CB54D}.Release|Any CPU.Build.0 = Release|Any CPU {9469D87B-126E-4338-92E3-701F762CB54D}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection EndGlobalSection

View File

@@ -4,6 +4,7 @@
<AppendTargetFrameworkToOutputPath>true</AppendTargetFrameworkToOutputPath> <AppendTargetFrameworkToOutputPath>true</AppendTargetFrameworkToOutputPath>
<CopyRefAssembliesToPublishDirectory>false</CopyRefAssembliesToPublishDirectory> <CopyRefAssembliesToPublishDirectory>false</CopyRefAssembliesToPublishDirectory>
<IncludeSourceRevisionInInformationalVersion>false</IncludeSourceRevisionInInformationalVersion>
<GenerateDocumentationFile>true</GenerateDocumentationFile> <GenerateDocumentationFile>true</GenerateDocumentationFile>
<IncludeSymbols>true</IncludeSymbols> <IncludeSymbols>true</IncludeSymbols>
@@ -22,4 +23,20 @@
<Copyright>© {copyright:2020-} AM.WD</Copyright> <Copyright>© {copyright:2020-} AM.WD</Copyright>
<PackageLicenseExpression>MIT</PackageLicenseExpression> <PackageLicenseExpression>MIT</PackageLicenseExpression>
</PropertyGroup> </PropertyGroup>
<PropertyGroup Condition="'$(CI)' == 'true'">
<ContinuousIntegrationBuild>true</ContinuousIntegrationBuild>
</PropertyGroup>
<ItemGroup Condition="'$(CI)' == 'true'">
<SourceLinkGitLabHost Include="$(CI_SERVER_HOST)" Version="$(CI_SERVER_VERSION)" />
<PackageReference Include="Microsoft.SourceLink.GitLab" Version="1.1.1" PrivateAssets="all" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="AMWD.NetRevisionTask" Version="1.1.0">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
</ItemGroup>
</Project> </Project>

View File

@@ -1,13 +0,0 @@
<Project>
<Target Condition="'$(Configuration)' == 'DebugLocal'" Name="CopyPackageToLocal" AfterTargets="Pack">
<Delete Files="D:\NuGetLocal\$(PackageId).$(PackageVersion).nupkg" />
<Delete Files="D:\NuGetLocal\$(PackageId).$(PackageVersion).snupkg" />
<Copy SourceFiles="$(MSBuildProjectDirectory)\bin\$(Configuration)\$(PackageId).$(PackageVersion).nupkg" DestinationFolder="D:\NuGetLocal" />
<Copy SourceFiles="$(MSBuildProjectDirectory)\bin\$(Configuration)\$(PackageId).$(PackageVersion).snupkg" DestinationFolder="D:\NuGetLocal" />
</Target>
<Target Condition="'$(Configuration)' == 'DebugLocal'" Name="PushToLocalFeed" AfterTargets="Pack">
<Exec Command="dotnet nuget push -s https://nuget.syshorst.de/v3/index.json $(MSBuildProjectDirectory)\bin\$(Configuration)\$(PackageId).$(PackageVersion).nupkg" />
</Target>
</Project>

View File

@@ -8,7 +8,7 @@ To save time, they are all packed to NuGet packages.
| AMWD.Common | ![https://nuget.am-wd.de/packages/amwd.common/](https://services.am-wd.de/nuget.php?package=AMWD.Common) | | AMWD.Common | ![https://nuget.am-wd.de/packages/amwd.common/](https://services.am-wd.de/nuget.php?package=AMWD.Common) |
| AMWD.Common.AspNetCore | ![https://nuget.am-wd.de/packages/amwd.common.aspnetcore/](https://services.am-wd.de/nuget.php?package=AMWD.Common.AspNetCore) | | AMWD.Common.AspNetCore | ![https://nuget.am-wd.de/packages/amwd.common.aspnetcore/](https://services.am-wd.de/nuget.php?package=AMWD.Common.AspNetCore) |
| AMWD.Common.EntityFrameworkCore | ![https://nuget.am-wd.de/packages/amwd.common.entityframeworkcore/](https://services.am-wd.de/nuget.php?package=AMWD.Common.EntityFrameworkCore) | | AMWD.Common.EntityFrameworkCore | ![https://nuget.am-wd.de/packages/amwd.common.entityframeworkcore/](https://services.am-wd.de/nuget.php?package=AMWD.Common.EntityFrameworkCore) |
| AMWD.Common.Moq | ![https://nuget.am-wd.de/packages/amwd.common.moq/](https://services.am-wd.de/nuget.php?package=AMWD.Common.Moq) | | AMWD.Common.Test | ![https://nuget.am-wd.de/packages/amwd.common.test/](https://services.am-wd.de/nuget.php?package=AMWD.Common.Test) |
| CI / CD | ![https://git.am-wd.de/AM.WD/common/-/pipelines](https://git.am-wd.de/AM.WD/common/badges/main/pipeline.svg?style=flat-square) ![https://git.am-wd.de/AM.WD/common/-/tree/main](https://git.am-wd.de/AM.WD/common/badges/main/coverage.svg?style=flat-square) | | CI / CD | ![https://git.am-wd.de/AM.WD/common/-/pipelines](https://git.am-wd.de/AM.WD/common/badges/main/pipeline.svg?style=flat-square) ![https://git.am-wd.de/AM.WD/common/-/tree/main](https://git.am-wd.de/AM.WD/common/badges/main/coverage.svg?style=flat-square) |
## Documentation ## Documentation
@@ -22,8 +22,8 @@ Create a `nuget.config` file in your root project folder (where the `.sln` file
```xml ```xml
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<configuration> <configuration>
<packageSources> <packageSources>
<add key="AM.WD" value="https://nuget.am-wd.de/v3/index.json" /> <add key="AM.WD" value="https://nuget.am-wd.de/v3/index.json" />
</packageSources> </packageSources>
</configuration> </configuration>
``` ```

View File

@@ -5,7 +5,7 @@ using System.Security.Claims;
using System.Text; using System.Text;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using AMWD.Common.AspNetCore.BasicAuthentication; using AMWD.Common.AspNetCore.Security.BasicAuthentication;
using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
@@ -22,28 +22,28 @@ namespace UnitTests.AspNetCore.Attributes
[System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage]
public class BasicAuthenticationAttributeTests public class BasicAuthenticationAttributeTests
{ {
private Mock<IHeaderDictionary> requestHeaderMock; private Mock<IHeaderDictionary> _requestHeaderMock;
private Mock<IHeaderDictionary> responseHeaderMock; private Mock<IHeaderDictionary> _responseHeaderMock;
private Mock<HttpRequest> requestMock; private Mock<HttpRequest> _requestMock;
private Mock<HttpResponse> responseMock; private Mock<HttpResponse> _responseMock;
private Mock<HttpContext> contextMock; private Mock<HttpContext> _contextMock;
private Dictionary<string, string> requestHeaders; private Dictionary<string, string> _requestHeaders;
private string validatorRealm; private string _validatorRealm;
private ClaimsPrincipal validatorResult; private ClaimsPrincipal _validatorResult;
private string responseHeaderAuthCallback; private string _responseHeaderAuthCallback;
[TestInitialize] [TestInitialize]
public void InitializeTest() public void InitializeTest()
{ {
requestHeaders = new Dictionary<string, string>(); _requestHeaders = [];
validatorRealm = null; _validatorRealm = null;
validatorResult = null; _validatorResult = null;
responseHeaderAuthCallback = null; _responseHeaderAuthCallback = null;
} }
[TestMethod] [TestMethod]
@@ -55,7 +55,7 @@ namespace UnitTests.AspNetCore.Attributes
Username = "user", Username = "user",
Password = "password" Password = "password"
}; };
requestHeaders.Add("Authorization", $"Basic {Convert.ToBase64String(Encoding.UTF8.GetBytes($"{attribute.Username}:{attribute.Password}"))}"); _requestHeaders.Add("Authorization", $"Basic {Convert.ToBase64String(Encoding.UTF8.GetBytes($"{attribute.Username}:{attribute.Password}"))}");
var context = GetContext(); var context = GetContext();
@@ -64,7 +64,7 @@ namespace UnitTests.AspNetCore.Attributes
// assert // assert
Assert.IsNull(context.Result); Assert.IsNull(context.Result);
Assert.IsTrue(string.IsNullOrWhiteSpace(responseHeaderAuthCallback)); Assert.IsTrue(string.IsNullOrWhiteSpace(_responseHeaderAuthCallback));
} }
[TestMethod] [TestMethod]
@@ -72,8 +72,8 @@ namespace UnitTests.AspNetCore.Attributes
{ {
// arrange // arrange
var attribute = new BasicAuthenticationAttribute(); var attribute = new BasicAuthenticationAttribute();
requestHeaders.Add("Authorization", $"Basic {Convert.ToBase64String(Encoding.UTF8.GetBytes($"{attribute.Username}:{attribute.Password}"))}"); _requestHeaders.Add("Authorization", $"Basic {Convert.ToBase64String(Encoding.UTF8.GetBytes($"{attribute.Username}:{attribute.Password}"))}");
validatorResult = new ClaimsPrincipal(); _validatorResult = new ClaimsPrincipal();
var context = GetContext(hasValidator: true); var context = GetContext(hasValidator: true);
@@ -82,7 +82,7 @@ namespace UnitTests.AspNetCore.Attributes
// assert // assert
Assert.IsNull(context.Result); Assert.IsNull(context.Result);
Assert.IsTrue(string.IsNullOrWhiteSpace(responseHeaderAuthCallback)); Assert.IsTrue(string.IsNullOrWhiteSpace(_responseHeaderAuthCallback));
} }
[TestMethod] [TestMethod]
@@ -101,7 +101,7 @@ namespace UnitTests.AspNetCore.Attributes
// assert // assert
Assert.IsNull(context.Result); Assert.IsNull(context.Result);
Assert.IsTrue(string.IsNullOrWhiteSpace(responseHeaderAuthCallback)); Assert.IsTrue(string.IsNullOrWhiteSpace(_responseHeaderAuthCallback));
} }
[TestMethod] [TestMethod]
@@ -123,8 +123,8 @@ namespace UnitTests.AspNetCore.Attributes
Assert.IsTrue(context.Result is StatusCodeResult); Assert.IsTrue(context.Result is StatusCodeResult);
Assert.AreEqual(401, ((StatusCodeResult)context.Result).StatusCode); Assert.AreEqual(401, ((StatusCodeResult)context.Result).StatusCode);
Assert.IsFalse(string.IsNullOrWhiteSpace(responseHeaderAuthCallback)); Assert.IsFalse(string.IsNullOrWhiteSpace(_responseHeaderAuthCallback));
Assert.AreEqual("Basic", responseHeaderAuthCallback); Assert.AreEqual("Basic", _responseHeaderAuthCallback);
} }
[TestMethod] [TestMethod]
@@ -147,8 +147,8 @@ namespace UnitTests.AspNetCore.Attributes
Assert.IsTrue(context.Result is StatusCodeResult); Assert.IsTrue(context.Result is StatusCodeResult);
Assert.AreEqual(401, ((StatusCodeResult)context.Result).StatusCode); Assert.AreEqual(401, ((StatusCodeResult)context.Result).StatusCode);
Assert.IsFalse(string.IsNullOrWhiteSpace(responseHeaderAuthCallback)); Assert.IsFalse(string.IsNullOrWhiteSpace(_responseHeaderAuthCallback));
Assert.AreEqual("Basic realm=\"re:alm\"", responseHeaderAuthCallback); Assert.AreEqual("Basic realm=\"re:alm\"", _responseHeaderAuthCallback);
} }
[TestMethod] [TestMethod]
@@ -160,7 +160,7 @@ namespace UnitTests.AspNetCore.Attributes
Username = "user", Username = "user",
Password = "password" Password = "password"
}; };
requestHeaders.Add("Authorization", $"Basic {Convert.ToBase64String(Encoding.UTF8.GetBytes($"{attribute.Username}a:{attribute.Password}"))}"); _requestHeaders.Add("Authorization", $"Basic {Convert.ToBase64String(Encoding.UTF8.GetBytes($"{attribute.Username}a:{attribute.Password}"))}");
var context = GetContext(); var context = GetContext();
// act // act
@@ -171,8 +171,8 @@ namespace UnitTests.AspNetCore.Attributes
Assert.IsTrue(context.Result is StatusCodeResult); Assert.IsTrue(context.Result is StatusCodeResult);
Assert.AreEqual(401, ((StatusCodeResult)context.Result).StatusCode); Assert.AreEqual(401, ((StatusCodeResult)context.Result).StatusCode);
Assert.IsFalse(string.IsNullOrWhiteSpace(responseHeaderAuthCallback)); Assert.IsFalse(string.IsNullOrWhiteSpace(_responseHeaderAuthCallback));
Assert.AreEqual("Basic", responseHeaderAuthCallback); Assert.AreEqual("Basic", _responseHeaderAuthCallback);
} }
[TestMethod] [TestMethod]
@@ -184,7 +184,7 @@ namespace UnitTests.AspNetCore.Attributes
Username = "user", Username = "user",
Password = "password" Password = "password"
}; };
requestHeaders.Add("Authorization", $"Basic {Convert.ToBase64String(Encoding.UTF8.GetBytes($"{attribute.Username}:{attribute.Password}a"))}"); _requestHeaders.Add("Authorization", $"Basic {Convert.ToBase64String(Encoding.UTF8.GetBytes($"{attribute.Username}:{attribute.Password}a"))}");
var context = GetContext(); var context = GetContext();
// act // act
@@ -195,8 +195,8 @@ namespace UnitTests.AspNetCore.Attributes
Assert.IsTrue(context.Result is StatusCodeResult); Assert.IsTrue(context.Result is StatusCodeResult);
Assert.AreEqual(401, ((StatusCodeResult)context.Result).StatusCode); Assert.AreEqual(401, ((StatusCodeResult)context.Result).StatusCode);
Assert.IsFalse(string.IsNullOrWhiteSpace(responseHeaderAuthCallback)); Assert.IsFalse(string.IsNullOrWhiteSpace(_responseHeaderAuthCallback));
Assert.AreEqual("Basic", responseHeaderAuthCallback); Assert.AreEqual("Basic", _responseHeaderAuthCallback);
} }
[TestMethod] [TestMethod]
@@ -207,7 +207,7 @@ namespace UnitTests.AspNetCore.Attributes
{ {
Realm = "attribute" Realm = "attribute"
}; };
requestHeaders.Add("Authorization", $"Basic {Convert.ToBase64String(Encoding.UTF8.GetBytes($"{attribute.Username}:{attribute.Password}"))}"); _requestHeaders.Add("Authorization", $"Basic {Convert.ToBase64String(Encoding.UTF8.GetBytes($"{attribute.Username}:{attribute.Password}"))}");
var context = GetContext(hasValidator: true); var context = GetContext(hasValidator: true);
// act // act
@@ -218,17 +218,17 @@ namespace UnitTests.AspNetCore.Attributes
Assert.IsTrue(context.Result is StatusCodeResult); Assert.IsTrue(context.Result is StatusCodeResult);
Assert.AreEqual(401, ((StatusCodeResult)context.Result).StatusCode); Assert.AreEqual(401, ((StatusCodeResult)context.Result).StatusCode);
Assert.IsFalse(string.IsNullOrWhiteSpace(responseHeaderAuthCallback)); Assert.IsFalse(string.IsNullOrWhiteSpace(_responseHeaderAuthCallback));
Assert.AreEqual("Basic realm=\"attribute\"", responseHeaderAuthCallback); Assert.AreEqual("Basic realm=\"attribute\"", _responseHeaderAuthCallback);
} }
[TestMethod] [TestMethod]
public async Task ShouldAskOnValidatorWithRealmOnValidator() public async Task ShouldAskOnValidatorWithRealmOnValidator()
{ {
// arrange // arrange
validatorRealm = "validator"; _validatorRealm = "validator";
var attribute = new BasicAuthenticationAttribute(); var attribute = new BasicAuthenticationAttribute();
requestHeaders.Add("Authorization", $"Basic {Convert.ToBase64String(Encoding.UTF8.GetBytes($"{attribute.Username}:{attribute.Password}"))}"); _requestHeaders.Add("Authorization", $"Basic {Convert.ToBase64String(Encoding.UTF8.GetBytes($"{attribute.Username}:{attribute.Password}"))}");
var context = GetContext(hasValidator: true); var context = GetContext(hasValidator: true);
// act // act
@@ -239,8 +239,8 @@ namespace UnitTests.AspNetCore.Attributes
Assert.IsTrue(context.Result is StatusCodeResult); Assert.IsTrue(context.Result is StatusCodeResult);
Assert.AreEqual(401, ((StatusCodeResult)context.Result).StatusCode); Assert.AreEqual(401, ((StatusCodeResult)context.Result).StatusCode);
Assert.IsFalse(string.IsNullOrWhiteSpace(responseHeaderAuthCallback)); Assert.IsFalse(string.IsNullOrWhiteSpace(_responseHeaderAuthCallback));
Assert.AreEqual("Basic realm=\"validator\"", responseHeaderAuthCallback); Assert.AreEqual("Basic realm=\"validator\"", _responseHeaderAuthCallback);
} }
[TestMethod] [TestMethod]
@@ -248,7 +248,7 @@ namespace UnitTests.AspNetCore.Attributes
{ {
// arrange // arrange
var attribute = new BasicAuthenticationAttribute(); var attribute = new BasicAuthenticationAttribute();
requestHeaders.Add("Authorization", $"Basic {Convert.ToBase64String(Encoding.UTF8.GetBytes($"{attribute.Username}"))}"); _requestHeaders.Add("Authorization", $"Basic {Convert.ToBase64String(Encoding.UTF8.GetBytes($"{attribute.Username}"))}");
var context = GetContext(); var context = GetContext();
// act // act
@@ -262,34 +262,38 @@ namespace UnitTests.AspNetCore.Attributes
private AuthorizationFilterContext GetContext(bool isAnonymousAllowed = false, bool hasValidator = false) private AuthorizationFilterContext GetContext(bool isAnonymousAllowed = false, bool hasValidator = false)
{ {
requestHeaderMock = new Mock<IHeaderDictionary>(); _requestHeaderMock = new Mock<IHeaderDictionary>();
foreach (var header in requestHeaders) foreach (var header in _requestHeaders)
{ {
requestHeaderMock StringValues outVal = header.Value;
_requestHeaderMock
.Setup(h => h.ContainsKey(header.Key)) .Setup(h => h.ContainsKey(header.Key))
.Returns(true); .Returns(true);
requestHeaderMock _requestHeaderMock
.Setup(h => h[header.Key]) .Setup(h => h[header.Key])
.Returns(header.Value); .Returns(header.Value);
_requestHeaderMock
.Setup(h => h.TryGetValue(header.Key, out outVal))
.Returns(true);
} }
responseHeaderMock = new Mock<IHeaderDictionary>(); _responseHeaderMock = new Mock<IHeaderDictionary>();
responseHeaderMock _responseHeaderMock
.SetupSet(h => h["WWW-Authenticate"] = It.IsAny<StringValues>()) .SetupSet(h => h.WWWAuthenticate = It.IsAny<StringValues>())
.Callback<string, StringValues>((key, value) => .Callback<StringValues>((value) =>
{ {
responseHeaderAuthCallback = value; _responseHeaderAuthCallback = value;
}); });
requestMock = new Mock<HttpRequest>(); _requestMock = new Mock<HttpRequest>();
requestMock _requestMock
.Setup(r => r.Headers) .Setup(r => r.Headers)
.Returns(requestHeaderMock.Object); .Returns(_requestHeaderMock.Object);
responseMock = new Mock<HttpResponse>(); _responseMock = new Mock<HttpResponse>();
responseMock _responseMock
.Setup(r => r.Headers) .Setup(r => r.Headers)
.Returns(responseHeaderMock.Object); .Returns(_responseHeaderMock.Object);
var requestServicesMock = new Mock<IServiceProvider>(); var requestServicesMock = new Mock<IServiceProvider>();
@@ -298,10 +302,10 @@ namespace UnitTests.AspNetCore.Attributes
var validatorMock = new Mock<IBasicAuthenticationValidator>(); var validatorMock = new Mock<IBasicAuthenticationValidator>();
validatorMock validatorMock
.Setup(v => v.Realm) .Setup(v => v.Realm)
.Returns(validatorRealm); .Returns(_validatorRealm);
validatorMock validatorMock
.Setup(v => v.ValidateAsync(It.IsAny<string>(), It.IsAny<string>(), It.IsAny<IPAddress>(), It.IsAny<CancellationToken>())) .Setup(v => v.ValidateAsync(It.IsAny<string>(), It.IsAny<string>(), It.IsAny<IPAddress>(), It.IsAny<CancellationToken>()))
.ReturnsAsync(validatorResult); .ReturnsAsync(_validatorResult);
requestServicesMock requestServicesMock
.Setup(rs => rs.GetService(typeof(IBasicAuthenticationValidator))) .Setup(rs => rs.GetService(typeof(IBasicAuthenticationValidator)))
@@ -313,20 +317,20 @@ namespace UnitTests.AspNetCore.Attributes
.Setup(ci => ci.RemoteIpAddress) .Setup(ci => ci.RemoteIpAddress)
.Returns(IPAddress.Loopback); .Returns(IPAddress.Loopback);
contextMock = new Mock<HttpContext>(); _contextMock = new Mock<HttpContext>();
contextMock _contextMock
.Setup(c => c.Request) .Setup(c => c.Request)
.Returns(requestMock.Object); .Returns(_requestMock.Object);
contextMock _contextMock
.Setup(c => c.Response) .Setup(c => c.Response)
.Returns(responseMock.Object); .Returns(_responseMock.Object);
contextMock _contextMock
.Setup(c => c.RequestServices) .Setup(c => c.RequestServices)
.Returns(requestServicesMock.Object); .Returns(requestServicesMock.Object);
contextMock _contextMock
.Setup(c => c.Connection) .Setup(c => c.Connection)
.Returns(connectionInfoMock.Object); .Returns(connectionInfoMock.Object);
contextMock _contextMock
.Setup(c => c.RequestAborted) .Setup(c => c.RequestAborted)
.Returns(CancellationToken.None); .Returns(CancellationToken.None);
@@ -341,7 +345,7 @@ namespace UnitTests.AspNetCore.Attributes
return new AuthorizationFilterContext(new ActionContext return new AuthorizationFilterContext(new ActionContext
{ {
HttpContext = contextMock.Object, HttpContext = _contextMock.Object,
RouteData = routeDataMock.Object, RouteData = routeDataMock.Object,
ActionDescriptor = actionDescriptor, ActionDescriptor = actionDescriptor,
}, new List<IFilterMetadata>()); }, new List<IFilterMetadata>());

View File

@@ -14,22 +14,22 @@ namespace UnitTests.AspNetCore.Attributes
{ {
[TestClass] [TestClass]
[System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage]
public class IPWhitelistAttributeTests public class IPAllowListAttributeTests
{ {
private Dictionary<string, string> requestHeaders; private Dictionary<string, string> _requestHeaders;
private Dictionary<object, object> itemsCallback; private Dictionary<object, object> _itemsCallback;
private string configKey; private string _configKey;
private bool configExists; private bool _configExists;
private List<string> allowedIpsConfig; private List<string> _allowedIpsConfig;
[TestInitialize] [TestInitialize]
public void InitializeTest() public void InitializeTest()
{ {
requestHeaders = new Dictionary<string, string>(); _requestHeaders = [];
itemsCallback = new Dictionary<object, object>(); _itemsCallback = [];
configKey = null; _configKey = null;
configExists = false; _configExists = false;
allowedIpsConfig = new List<string>(); _allowedIpsConfig = [];
} }
[TestMethod] [TestMethod]
@@ -37,7 +37,7 @@ namespace UnitTests.AspNetCore.Attributes
{ {
// arrange // arrange
var remote = IPAddress.Parse("192.168.178.1"); var remote = IPAddress.Parse("192.168.178.1");
var attribute = new IPWhitelistAttribute(); var attribute = new IPAllowListAttribute();
var context = GetContext(remote); var context = GetContext(remote);
// act // act
@@ -48,8 +48,8 @@ namespace UnitTests.AspNetCore.Attributes
Assert.IsTrue(context.Result is StatusCodeResult); Assert.IsTrue(context.Result is StatusCodeResult);
Assert.AreEqual(403, ((StatusCodeResult)context.Result).StatusCode); Assert.AreEqual(403, ((StatusCodeResult)context.Result).StatusCode);
Assert.AreEqual(1, itemsCallback.Count); Assert.AreEqual(1, _itemsCallback.Count);
Assert.AreEqual(remote, itemsCallback["RemoteAddress"]); Assert.AreEqual(remote, _itemsCallback["RemoteAddress"]);
} }
[TestMethod] [TestMethod]
@@ -57,7 +57,7 @@ namespace UnitTests.AspNetCore.Attributes
{ {
// arrange // arrange
var remote = IPAddress.Parse("192.168.178.1"); var remote = IPAddress.Parse("192.168.178.1");
var attribute = new IPWhitelistAttribute var attribute = new IPAllowListAttribute
{ {
AllowedIpAddresses = "192.168.178:1" AllowedIpAddresses = "192.168.178:1"
}; };
@@ -71,15 +71,15 @@ namespace UnitTests.AspNetCore.Attributes
Assert.IsTrue(context.Result is StatusCodeResult); Assert.IsTrue(context.Result is StatusCodeResult);
Assert.AreEqual(403, ((StatusCodeResult)context.Result).StatusCode); Assert.AreEqual(403, ((StatusCodeResult)context.Result).StatusCode);
Assert.AreEqual(1, itemsCallback.Count); Assert.AreEqual(1, _itemsCallback.Count);
Assert.AreEqual(remote, itemsCallback["RemoteAddress"]); Assert.AreEqual(remote, _itemsCallback["RemoteAddress"]);
} }
[TestMethod] [TestMethod]
public void ShouldAllowLocalAccess() public void ShouldAllowLocalAccess()
{ {
// arrange // arrange
var attribute = new IPWhitelistAttribute(); var attribute = new IPAllowListAttribute();
var context = GetContext(); var context = GetContext();
// act // act
@@ -87,15 +87,15 @@ namespace UnitTests.AspNetCore.Attributes
// assert // assert
Assert.IsNull(context.Result); Assert.IsNull(context.Result);
Assert.AreEqual(1, itemsCallback.Count); Assert.AreEqual(1, _itemsCallback.Count);
Assert.AreEqual(IPAddress.Loopback, itemsCallback["RemoteAddress"]); Assert.AreEqual(IPAddress.Loopback, _itemsCallback["RemoteAddress"]);
} }
[TestMethod] [TestMethod]
public void ShouldDenyLocalAccess() public void ShouldDenyLocalAccess()
{ {
// arrange // arrange
var attribute = new IPWhitelistAttribute var attribute = new IPAllowListAttribute
{ {
AllowLocalAccess = false AllowLocalAccess = false
}; };
@@ -109,8 +109,8 @@ namespace UnitTests.AspNetCore.Attributes
Assert.IsTrue(context.Result is StatusCodeResult); Assert.IsTrue(context.Result is StatusCodeResult);
Assert.AreEqual(403, ((StatusCodeResult)context.Result).StatusCode); Assert.AreEqual(403, ((StatusCodeResult)context.Result).StatusCode);
Assert.AreEqual(1, itemsCallback.Count); Assert.AreEqual(1, _itemsCallback.Count);
Assert.AreEqual(IPAddress.Loopback, itemsCallback["RemoteAddress"]); Assert.AreEqual(IPAddress.Loopback, _itemsCallback["RemoteAddress"]);
} }
[DataTestMethod] [DataTestMethod]
@@ -120,7 +120,7 @@ namespace UnitTests.AspNetCore.Attributes
{ {
// arrange // arrange
var remote = IPAddress.Parse(address); var remote = IPAddress.Parse(address);
var attribute = new IPWhitelistAttribute var attribute = new IPAllowListAttribute
{ {
AllowLocalAccess = false, AllowLocalAccess = false,
AllowedIpAddresses = ",127.0.0.0/8,192.168.178.10" AllowedIpAddresses = ",127.0.0.0/8,192.168.178.10"
@@ -142,22 +142,22 @@ namespace UnitTests.AspNetCore.Attributes
Assert.AreEqual(403, ((StatusCodeResult)context.Result).StatusCode); Assert.AreEqual(403, ((StatusCodeResult)context.Result).StatusCode);
} }
Assert.AreEqual(1, itemsCallback.Count); Assert.AreEqual(1, _itemsCallback.Count);
Assert.AreEqual(remote, itemsCallback["RemoteAddress"]); Assert.AreEqual(remote, _itemsCallback["RemoteAddress"]);
} }
[TestMethod] [TestMethod]
public void ShouldAllowLocalAccessConfig() public void ShouldAllowLocalAccessConfig()
{ {
// arrange // arrange
configKey = "White:List"; _configKey = "White:List";
configExists = true; _configExists = true;
allowedIpsConfig.Add("127.0.0.0/8"); _allowedIpsConfig.Add("127.0.0.0/8");
allowedIpsConfig.Add("192.168.178.10"); _allowedIpsConfig.Add("192.168.178.10");
var attribute = new IPWhitelistAttribute var attribute = new IPAllowListAttribute
{ {
AllowLocalAccess = true, AllowLocalAccess = true,
ConfigurationKey = configKey ConfigurationKey = _configKey
}; };
var context = GetContext(); var context = GetContext();
@@ -166,22 +166,22 @@ namespace UnitTests.AspNetCore.Attributes
// assert // assert
Assert.IsNull(context.Result); Assert.IsNull(context.Result);
Assert.AreEqual(1, itemsCallback.Count); Assert.AreEqual(1, _itemsCallback.Count);
Assert.AreEqual(IPAddress.Loopback, itemsCallback["RemoteAddress"]); Assert.AreEqual(IPAddress.Loopback, _itemsCallback["RemoteAddress"]);
} }
[TestMethod] [TestMethod]
public void ShouldDenyLocalAccessConfig() public void ShouldDenyLocalAccessConfig()
{ {
// arrange // arrange
configKey = "White:List"; _configKey = "White:List";
configExists = true; _configExists = true;
allowedIpsConfig.Add(""); _allowedIpsConfig.Add("");
allowedIpsConfig.Add("192.168.178.10"); _allowedIpsConfig.Add("192.168.178.10");
var attribute = new IPWhitelistAttribute var attribute = new IPAllowListAttribute
{ {
AllowLocalAccess = false, AllowLocalAccess = false,
ConfigurationKey = configKey ConfigurationKey = _configKey
}; };
var context = GetContext(); var context = GetContext();
@@ -193,8 +193,8 @@ namespace UnitTests.AspNetCore.Attributes
Assert.IsTrue(context.Result is StatusCodeResult); Assert.IsTrue(context.Result is StatusCodeResult);
Assert.AreEqual(403, ((StatusCodeResult)context.Result).StatusCode); Assert.AreEqual(403, ((StatusCodeResult)context.Result).StatusCode);
Assert.AreEqual(1, itemsCallback.Count); Assert.AreEqual(1, _itemsCallback.Count);
Assert.AreEqual(IPAddress.Loopback, itemsCallback["RemoteAddress"]); Assert.AreEqual(IPAddress.Loopback, _itemsCallback["RemoteAddress"]);
} }
[DataTestMethod] [DataTestMethod]
@@ -203,13 +203,13 @@ namespace UnitTests.AspNetCore.Attributes
public void ShouldAllowSpecificAddressConfig(string address) public void ShouldAllowSpecificAddressConfig(string address)
{ {
// arrange // arrange
configKey = "White:List"; _configKey = "White:List";
configExists = true; _configExists = true;
allowedIpsConfig.Add("192.168.178.10"); _allowedIpsConfig.Add("192.168.178.10");
var attribute = new IPWhitelistAttribute var attribute = new IPAllowListAttribute
{ {
AllowLocalAccess = false, AllowLocalAccess = false,
ConfigurationKey = configKey ConfigurationKey = _configKey
}; };
var remote = IPAddress.Parse(address); var remote = IPAddress.Parse(address);
var context = GetContext(remote); var context = GetContext(remote);
@@ -229,20 +229,20 @@ namespace UnitTests.AspNetCore.Attributes
Assert.AreEqual(403, ((StatusCodeResult)context.Result).StatusCode); Assert.AreEqual(403, ((StatusCodeResult)context.Result).StatusCode);
} }
Assert.AreEqual(1, itemsCallback.Count); Assert.AreEqual(1, _itemsCallback.Count);
Assert.AreEqual(remote, itemsCallback["RemoteAddress"]); Assert.AreEqual(remote, _itemsCallback["RemoteAddress"]);
} }
[TestMethod] [TestMethod]
public void ShouldDenyOnMissingConfiguration() public void ShouldDenyOnMissingConfiguration()
{ {
// arrange // arrange
configKey = "White:List"; _configKey = "White:List";
configExists = false; _configExists = false;
var attribute = new IPWhitelistAttribute var attribute = new IPAllowListAttribute
{ {
AllowLocalAccess = false, AllowLocalAccess = false,
ConfigurationKey = configKey ConfigurationKey = _configKey
}; };
var context = GetContext(); var context = GetContext();
@@ -254,14 +254,14 @@ namespace UnitTests.AspNetCore.Attributes
Assert.IsTrue(context.Result is StatusCodeResult); Assert.IsTrue(context.Result is StatusCodeResult);
Assert.AreEqual(403, ((StatusCodeResult)context.Result).StatusCode); Assert.AreEqual(403, ((StatusCodeResult)context.Result).StatusCode);
Assert.AreEqual(1, itemsCallback.Count); Assert.AreEqual(1, _itemsCallback.Count);
Assert.AreEqual(IPAddress.Loopback, itemsCallback["RemoteAddress"]); Assert.AreEqual(IPAddress.Loopback, _itemsCallback["RemoteAddress"]);
} }
private ActionExecutingContext GetContext(IPAddress remote = null) private ActionExecutingContext GetContext(IPAddress remote = null)
{ {
var requestHeaderMock = new Mock<IHeaderDictionary>(); var requestHeaderMock = new Mock<IHeaderDictionary>();
foreach (var header in requestHeaders) foreach (var header in _requestHeaders)
{ {
requestHeaderMock requestHeaderMock
.Setup(h => h.ContainsKey(header.Key)) .Setup(h => h.ContainsKey(header.Key))
@@ -287,11 +287,11 @@ namespace UnitTests.AspNetCore.Attributes
var itemsMock = new Mock<IDictionary<object, object>>(); var itemsMock = new Mock<IDictionary<object, object>>();
itemsMock itemsMock
.SetupSet(i => i[It.IsAny<object>()] = It.IsAny<object>()) .SetupSet(i => i[It.IsAny<object>()] = It.IsAny<object>())
.Callback<object, object>((key, val) => itemsCallback.Add(key, val)); .Callback<object, object>((key, val) => _itemsCallback.Add(key, val));
var configurationMock = new Mock<IConfiguration>(); var configurationMock = new Mock<IConfiguration>();
var children = new List<IConfigurationSection>(); var children = new List<IConfigurationSection>();
foreach (string ipAddress in allowedIpsConfig) foreach (string ipAddress in _allowedIpsConfig)
{ {
var csm = new Mock<IConfigurationSection>(); var csm = new Mock<IConfigurationSection>();
csm.Setup(cs => cs.Value).Returns(ipAddress); csm.Setup(cs => cs.Value).Returns(ipAddress);
@@ -305,8 +305,8 @@ namespace UnitTests.AspNetCore.Attributes
.Returns(children); .Returns(children);
configurationMock configurationMock
.Setup(c => c.GetSection(configKey)) .Setup(c => c.GetSection(_configKey))
.Returns(configExists ? configSectionMock.Object : null); .Returns(_configExists ? configSectionMock.Object : null);
var requestServicesMock = new Mock<IServiceProvider>(); var requestServicesMock = new Mock<IServiceProvider>();
requestServicesMock requestServicesMock

View File

@@ -14,22 +14,22 @@ namespace UnitTests.AspNetCore.Attributes
{ {
[TestClass] [TestClass]
[System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage]
public class IPBlacklistAttributeTests public class IPBlockListAttributeTests
{ {
private Dictionary<string, string> requestHeaders; private Dictionary<string, string> _requestHeaders;
private Dictionary<object, object> itemsCallback; private Dictionary<object, object> _itemsCallback;
private string configKey; private string _configKey;
private bool configExists; private bool _configExists;
private List<string> restrictedIpsConfig; private List<string> _restrictedIpsConfig;
[TestInitialize] [TestInitialize]
public void InitializeTest() public void InitializeTest()
{ {
requestHeaders = new Dictionary<string, string>(); _requestHeaders = [];
itemsCallback = new Dictionary<object, object>(); _itemsCallback = [];
configKey = null; _configKey = null;
configExists = false; _configExists = false;
restrictedIpsConfig = new List<string>(); _restrictedIpsConfig = [];
} }
[TestMethod] [TestMethod]
@@ -37,7 +37,7 @@ namespace UnitTests.AspNetCore.Attributes
{ {
// arrange // arrange
var remote = IPAddress.Parse("192.168.178.1"); var remote = IPAddress.Parse("192.168.178.1");
var attribute = new IPBlacklistAttribute(); var attribute = new IPBlockListAttribute();
var context = GetContext(remote); var context = GetContext(remote);
// act // act
@@ -45,8 +45,8 @@ namespace UnitTests.AspNetCore.Attributes
// assert // assert
Assert.IsNull(context.Result); Assert.IsNull(context.Result);
Assert.AreEqual(1, itemsCallback.Count); Assert.AreEqual(1, _itemsCallback.Count);
Assert.AreEqual(remote, itemsCallback["RemoteAddress"]); Assert.AreEqual(remote, _itemsCallback["RemoteAddress"]);
} }
[TestMethod] [TestMethod]
@@ -54,9 +54,9 @@ namespace UnitTests.AspNetCore.Attributes
{ {
// arrange // arrange
var remote = IPAddress.Parse("192.168.178.1"); var remote = IPAddress.Parse("192.168.178.1");
var attribute = new IPBlacklistAttribute var attribute = new IPBlockListAttribute
{ {
RestrictedIpAddresses = "192.168.178:1" BlockedIpAddresses = "192.168.178:1"
}; };
var context = GetContext(remote); var context = GetContext(remote);
@@ -65,18 +65,18 @@ namespace UnitTests.AspNetCore.Attributes
// assert // assert
Assert.IsNull(context.Result); Assert.IsNull(context.Result);
Assert.AreEqual(1, itemsCallback.Count); Assert.AreEqual(1, _itemsCallback.Count);
Assert.AreEqual(remote, itemsCallback["RemoteAddress"]); Assert.AreEqual(remote, _itemsCallback["RemoteAddress"]);
} }
[TestMethod] [TestMethod]
public void ShouldAllowLocalAccess() public void ShouldAllowLocalAccess()
{ {
// arrange // arrange
var attribute = new IPBlacklistAttribute var attribute = new IPBlockListAttribute
{ {
RestrictLocalAccess = false, BlockLocalAccess = false,
RestrictedIpAddresses = "127.0.0.0/8" BlockedIpAddresses = "127.0.0.0/8"
}; };
var context = GetContext(); var context = GetContext();
@@ -85,18 +85,18 @@ namespace UnitTests.AspNetCore.Attributes
// assert // assert
Assert.IsNull(context.Result); Assert.IsNull(context.Result);
Assert.AreEqual(1, itemsCallback.Count); Assert.AreEqual(1, _itemsCallback.Count);
Assert.AreEqual(IPAddress.Loopback, itemsCallback["RemoteAddress"]); Assert.AreEqual(IPAddress.Loopback, _itemsCallback["RemoteAddress"]);
} }
[TestMethod] [TestMethod]
public void ShouldBlockLocalAccess() public void ShouldBlockLocalAccess()
{ {
// arrange // arrange
var attribute = new IPBlacklistAttribute var attribute = new IPBlockListAttribute
{ {
RestrictLocalAccess = true, BlockLocalAccess = true,
RestrictedIpAddresses = ",127.0.0.0/8" BlockedIpAddresses = ",127.0.0.0/8"
}; };
var context = GetContext(); var context = GetContext();
@@ -108,8 +108,8 @@ namespace UnitTests.AspNetCore.Attributes
Assert.IsTrue(context.Result is StatusCodeResult); Assert.IsTrue(context.Result is StatusCodeResult);
Assert.AreEqual(403, ((StatusCodeResult)context.Result).StatusCode); Assert.AreEqual(403, ((StatusCodeResult)context.Result).StatusCode);
Assert.AreEqual(1, itemsCallback.Count); Assert.AreEqual(1, _itemsCallback.Count);
Assert.AreEqual(IPAddress.Loopback, itemsCallback["RemoteAddress"]); Assert.AreEqual(IPAddress.Loopback, _itemsCallback["RemoteAddress"]);
} }
[DataTestMethod] [DataTestMethod]
@@ -119,10 +119,10 @@ namespace UnitTests.AspNetCore.Attributes
{ {
// arrange // arrange
var remote = IPAddress.Parse(address); var remote = IPAddress.Parse(address);
var attribute = new IPBlacklistAttribute var attribute = new IPBlockListAttribute
{ {
RestrictLocalAccess = true, BlockLocalAccess = true,
RestrictedIpAddresses = "127.0.0.0/8,192.168.178.10" BlockedIpAddresses = "127.0.0.0/8,192.168.178.10"
}; };
var context = GetContext(remote); var context = GetContext(remote);
@@ -141,22 +141,22 @@ namespace UnitTests.AspNetCore.Attributes
Assert.IsNull(context.Result); Assert.IsNull(context.Result);
} }
Assert.AreEqual(1, itemsCallback.Count); Assert.AreEqual(1, _itemsCallback.Count);
Assert.AreEqual(remote, itemsCallback["RemoteAddress"]); Assert.AreEqual(remote, _itemsCallback["RemoteAddress"]);
} }
[TestMethod] [TestMethod]
public void ShouldAllowLocalAccessConfig() public void ShouldAllowLocalAccessConfig()
{ {
// arrange // arrange
configKey = "Black:List"; _configKey = "Black:List";
configExists = true; _configExists = true;
restrictedIpsConfig.Add("127.0.0.0/8"); _restrictedIpsConfig.Add("127.0.0.0/8");
restrictedIpsConfig.Add("192.168.178.10"); _restrictedIpsConfig.Add("192.168.178.10");
var attribute = new IPBlacklistAttribute var attribute = new IPBlockListAttribute
{ {
RestrictLocalAccess = false, BlockLocalAccess = false,
ConfigurationKey = configKey ConfigurationKey = _configKey
}; };
var context = GetContext(); var context = GetContext();
@@ -165,23 +165,23 @@ namespace UnitTests.AspNetCore.Attributes
// assert // assert
Assert.IsNull(context.Result); Assert.IsNull(context.Result);
Assert.AreEqual(1, itemsCallback.Count); Assert.AreEqual(1, _itemsCallback.Count);
Assert.AreEqual(IPAddress.Loopback, itemsCallback["RemoteAddress"]); Assert.AreEqual(IPAddress.Loopback, _itemsCallback["RemoteAddress"]);
} }
[TestMethod] [TestMethod]
public void ShouldBlockLocalAccessConfig() public void ShouldBlockLocalAccessConfig()
{ {
// arrange // arrange
configKey = "Black:List"; _configKey = "Black:List";
configExists = true; _configExists = true;
restrictedIpsConfig.Add(""); _restrictedIpsConfig.Add("");
restrictedIpsConfig.Add("127.0.0.0/8"); _restrictedIpsConfig.Add("127.0.0.0/8");
restrictedIpsConfig.Add("192.168.178.10"); _restrictedIpsConfig.Add("192.168.178.10");
var attribute = new IPBlacklistAttribute var attribute = new IPBlockListAttribute
{ {
RestrictLocalAccess = true, BlockLocalAccess = true,
ConfigurationKey = configKey ConfigurationKey = _configKey
}; };
var context = GetContext(); var context = GetContext();
@@ -193,8 +193,8 @@ namespace UnitTests.AspNetCore.Attributes
Assert.IsTrue(context.Result is StatusCodeResult); Assert.IsTrue(context.Result is StatusCodeResult);
Assert.AreEqual(403, ((StatusCodeResult)context.Result).StatusCode); Assert.AreEqual(403, ((StatusCodeResult)context.Result).StatusCode);
Assert.AreEqual(1, itemsCallback.Count); Assert.AreEqual(1, _itemsCallback.Count);
Assert.AreEqual(IPAddress.Loopback, itemsCallback["RemoteAddress"]); Assert.AreEqual(IPAddress.Loopback, _itemsCallback["RemoteAddress"]);
} }
[DataTestMethod] [DataTestMethod]
@@ -203,14 +203,14 @@ namespace UnitTests.AspNetCore.Attributes
public void ShouldBlockSpecificAddressConfig(string address) public void ShouldBlockSpecificAddressConfig(string address)
{ {
// arrange // arrange
configKey = "Black:List"; _configKey = "Black:List";
configExists = true; _configExists = true;
restrictedIpsConfig.Add("127.0.0.0/8"); _restrictedIpsConfig.Add("127.0.0.0/8");
restrictedIpsConfig.Add("192.168.178.10"); _restrictedIpsConfig.Add("192.168.178.10");
var attribute = new IPBlacklistAttribute var attribute = new IPBlockListAttribute
{ {
RestrictLocalAccess = true, BlockLocalAccess = true,
ConfigurationKey = configKey ConfigurationKey = _configKey
}; };
var remote = IPAddress.Parse(address); var remote = IPAddress.Parse(address);
var context = GetContext(remote); var context = GetContext(remote);
@@ -230,20 +230,20 @@ namespace UnitTests.AspNetCore.Attributes
Assert.IsNull(context.Result); Assert.IsNull(context.Result);
} }
Assert.AreEqual(1, itemsCallback.Count); Assert.AreEqual(1, _itemsCallback.Count);
Assert.AreEqual(remote, itemsCallback["RemoteAddress"]); Assert.AreEqual(remote, _itemsCallback["RemoteAddress"]);
} }
[TestMethod] [TestMethod]
public void ShouldAllowOnMissingConfiguration() public void ShouldAllowOnMissingConfiguration()
{ {
// arrange // arrange
configKey = "Black:List"; _configKey = "Black:List";
configExists = false; _configExists = false;
var attribute = new IPBlacklistAttribute var attribute = new IPBlockListAttribute
{ {
RestrictLocalAccess = true, BlockLocalAccess = true,
ConfigurationKey = configKey ConfigurationKey = _configKey
}; };
var context = GetContext(); var context = GetContext();
@@ -253,14 +253,14 @@ namespace UnitTests.AspNetCore.Attributes
// assert // assert
Assert.IsNull(context.Result); Assert.IsNull(context.Result);
Assert.AreEqual(1, itemsCallback.Count); Assert.AreEqual(1, _itemsCallback.Count);
Assert.AreEqual(IPAddress.Loopback, itemsCallback["RemoteAddress"]); Assert.AreEqual(IPAddress.Loopback, _itemsCallback["RemoteAddress"]);
} }
private ActionExecutingContext GetContext(IPAddress remote = null) private ActionExecutingContext GetContext(IPAddress remote = null)
{ {
var requestHeaderMock = new Mock<IHeaderDictionary>(); var requestHeaderMock = new Mock<IHeaderDictionary>();
foreach (var header in requestHeaders) foreach (var header in _requestHeaders)
{ {
requestHeaderMock requestHeaderMock
.Setup(h => h.ContainsKey(header.Key)) .Setup(h => h.ContainsKey(header.Key))
@@ -286,11 +286,11 @@ namespace UnitTests.AspNetCore.Attributes
var itemsMock = new Mock<IDictionary<object, object>>(); var itemsMock = new Mock<IDictionary<object, object>>();
itemsMock itemsMock
.SetupSet(i => i[It.IsAny<object>()] = It.IsAny<object>()) .SetupSet(i => i[It.IsAny<object>()] = It.IsAny<object>())
.Callback<object, object>((key, val) => itemsCallback.Add(key, val)); .Callback<object, object>((key, val) => _itemsCallback.Add(key, val));
var configurationMock = new Mock<IConfiguration>(); var configurationMock = new Mock<IConfiguration>();
var children = new List<IConfigurationSection>(); var children = new List<IConfigurationSection>();
foreach (string ipAddress in restrictedIpsConfig) foreach (string ipAddress in _restrictedIpsConfig)
{ {
var csm = new Mock<IConfigurationSection>(); var csm = new Mock<IConfigurationSection>();
csm.Setup(cs => cs.Value).Returns(ipAddress); csm.Setup(cs => cs.Value).Returns(ipAddress);
@@ -304,8 +304,8 @@ namespace UnitTests.AspNetCore.Attributes
.Returns(children); .Returns(children);
configurationMock configurationMock
.Setup(c => c.GetSection(configKey)) .Setup(c => c.GetSection(_configKey))
.Returns(configExists ? configSectionMock.Object : null); .Returns(_configExists ? configSectionMock.Object : null);
var requestServicesMock = new Mock<IServiceProvider>(); var requestServicesMock = new Mock<IServiceProvider>();
requestServicesMock requestServicesMock

View File

@@ -12,28 +12,30 @@ namespace UnitTests.AspNetCore.Extensions
[System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage]
public class HttpContextExtensionsTests public class HttpContextExtensionsTests
{ {
private Mock<ISession> sessionMock; private Mock<ISession> _sessionMock;
private string tokenName; private string _tokenFormName;
private string tokenValue; private string _tokenHeaderName;
private string _tokenValue;
private Dictionary<string, string> requestHeaders; private Dictionary<string, string> _requestHeaders;
private Dictionary<string, string> requestQueries; private Dictionary<string, string> _requestQueries;
private Dictionary<object, object> items; private Dictionary<object, object> _items;
private IPAddress remote; private IPAddress _remote;
[TestInitialize] [TestInitialize]
public void InitializeTests() public void InitializeTests()
{ {
tokenName = null; _tokenFormName = null;
tokenValue = null; _tokenHeaderName = null;
_tokenValue = null;
requestHeaders = new Dictionary<string, string>(); _requestHeaders = [];
requestQueries = new Dictionary<string, string>(); _requestQueries = [];
items = new Dictionary<object, object>(); _items = [];
remote = IPAddress.Loopback; _remote = IPAddress.Loopback;
} }
#region Antiforgery #region Antiforgery
@@ -42,34 +44,38 @@ namespace UnitTests.AspNetCore.Extensions
public void ShouldReturnAntiforgery() public void ShouldReturnAntiforgery()
{ {
// arrange // arrange
tokenName = "af-token"; _tokenFormName = "af-token";
tokenValue = "security_first"; _tokenHeaderName = "af-header";
_tokenValue = "security_first";
var context = GetContext(); var context = GetContext();
// act // act
var result = context.GetAntiforgeryToken(); var (formName, headerName, value) = context.GetAntiforgeryToken();
// assert // assert
Assert.AreEqual(tokenName, result.Name); Assert.AreEqual(_tokenFormName, formName);
Assert.AreEqual(tokenValue, result.Value); Assert.AreEqual(_tokenHeaderName, headerName);
Assert.AreEqual(_tokenValue, value);
} }
[TestMethod] [TestMethod]
public void ShouldReturnAntiforgeryNullService() public void ShouldReturnAntiforgeryNullService()
{ {
// arrange // arrange
tokenName = "af-token"; _tokenFormName = "af-token";
tokenValue = "security_first"; _tokenHeaderName = "af-header";
_tokenValue = "security_first";
var context = GetContext(hasAntiforgery: false); var context = GetContext(hasAntiforgery: false);
// act // act
var result = context.GetAntiforgeryToken(); var (formName, headerName, value) = context.GetAntiforgeryToken();
// assert // assert
Assert.AreEqual(null, result.Name); Assert.IsNull(formName);
Assert.AreEqual(null, result.Value); Assert.IsNull(headerName);
Assert.IsNull(value);
} }
[TestMethod] [TestMethod]
@@ -79,11 +85,12 @@ namespace UnitTests.AspNetCore.Extensions
var context = GetContext(); var context = GetContext();
// act // act
var result = context.GetAntiforgeryToken(); var (formName, headerName, value) = context.GetAntiforgeryToken();
// assert // assert
Assert.AreEqual(null, result.Name); Assert.IsNull(formName);
Assert.AreEqual(null, result.Value); Assert.IsNull(headerName);
Assert.IsNull(value);
} }
#endregion Antiforgery #endregion Antiforgery
@@ -94,7 +101,7 @@ namespace UnitTests.AspNetCore.Extensions
public void ShouldReturnRemoteAddress() public void ShouldReturnRemoteAddress()
{ {
// arrange // arrange
remote = IPAddress.Parse("1.2.3.4"); _remote = IPAddress.Parse("1.2.3.4");
var context = GetContext(); var context = GetContext();
@@ -102,16 +109,19 @@ namespace UnitTests.AspNetCore.Extensions
var result = context.GetRemoteIpAddress(); var result = context.GetRemoteIpAddress();
// assert // assert
Assert.AreEqual(remote, result); Assert.AreEqual(_remote, result);
} }
[TestMethod] [DataTestMethod]
public void ShouldReturnDefaultHeader() [DataRow("Cf-Connecting-Ip")]
[DataRow("X-Real-IP")]
[DataRow("X-Forwarded-For")]
public void ShouldReturnDefaultHeader(string headerName)
{ {
// arrange // arrange
remote = IPAddress.Parse("1.2.3.4"); _remote = IPAddress.Parse("1.2.3.4");
var header = IPAddress.Parse("5.6.7.8"); var header = IPAddress.Parse("5.6.7.8");
requestHeaders.Add("X-Forwarded-For", header.ToString()); _requestHeaders.Add(headerName, header.ToString());
var context = GetContext(); var context = GetContext();
@@ -119,7 +129,7 @@ namespace UnitTests.AspNetCore.Extensions
var result = context.GetRemoteIpAddress(); var result = context.GetRemoteIpAddress();
// assert // assert
Assert.AreNotEqual(remote, result); Assert.AreNotEqual(_remote, result);
Assert.AreEqual(header, result); Assert.AreEqual(header, result);
} }
@@ -127,18 +137,20 @@ namespace UnitTests.AspNetCore.Extensions
public void ShouldReturnCustomHeader() public void ShouldReturnCustomHeader()
{ {
// arrange // arrange
remote = IPAddress.Parse("1.2.3.4"); _remote = IPAddress.Parse("1.2.3.4");
string headerName = "FooBar"; string headerName = "FooBar";
var headerIp = IPAddress.Parse("5.6.7.8"); var headerIp = IPAddress.Parse("5.6.7.8");
requestHeaders.Add(headerName, headerIp.ToString());
_requestHeaders.Add(headerName, headerIp.ToString());
_requestHeaders.Add("X-Forwarded-For", _remote.ToString());
var context = GetContext(); var context = GetContext();
// act // act
var result = context.GetRemoteIpAddress(headerName: headerName); var result = context.GetRemoteIpAddress(ipHeaderName: headerName);
// assert // assert
Assert.AreNotEqual(remote, result); Assert.AreNotEqual(_remote, result);
Assert.AreEqual(headerIp, result); Assert.AreEqual(headerIp, result);
} }
@@ -146,8 +158,8 @@ namespace UnitTests.AspNetCore.Extensions
public void ShouldReturnAddressInvalidHeader() public void ShouldReturnAddressInvalidHeader()
{ {
// arrange // arrange
remote = IPAddress.Parse("1.2.3.4"); _remote = IPAddress.Parse("1.2.3.4");
requestHeaders.Add("X-Forwarded-For", "1.2.3:4"); _requestHeaders.Add("X-Forwarded-For", "1.2.3:4");
var context = GetContext(); var context = GetContext();
@@ -155,7 +167,44 @@ namespace UnitTests.AspNetCore.Extensions
var result = context.GetRemoteIpAddress(); var result = context.GetRemoteIpAddress();
// assert // assert
Assert.AreEqual(remote, result); Assert.AreEqual(_remote, result);
}
[TestMethod]
public void ShouldReturnFirstAddressOnMultipleProxies()
{
// arrange
_remote = IPAddress.Parse("1.2.3.4");
var header = IPAddress.Parse("5.6.7.8");
_requestHeaders.Add("X-Forwarded-For", $"{header}, 111.222.111.222");
var context = GetContext();
// act
var result = context.GetRemoteIpAddress();
// assert
Assert.AreNotEqual(_remote, result);
Assert.AreEqual(header, result);
}
[TestMethod]
public void ShouldReturnV4AddressOnMapped()
{
// arrange
_remote = IPAddress.Parse("1.2.3.4");
var header = IPAddress.Parse("::ffff:127.0.0.1");
_requestHeaders.Add("X-Forwarded-For", "::ffff:127.0.0.1");
var context = GetContext();
// act
var result = context.GetRemoteIpAddress();
// assert
Assert.AreNotEqual(_remote, result);
Assert.AreNotEqual(header, result);
Assert.AreEqual(header.MapToIPv4(), result);
} }
#endregion RemoteAddres #endregion RemoteAddres
@@ -166,7 +215,7 @@ namespace UnitTests.AspNetCore.Extensions
public void ShouldReturnTrueOnLocal() public void ShouldReturnTrueOnLocal()
{ {
// arrange // arrange
remote = IPAddress.Loopback; _remote = IPAddress.Loopback;
var context = GetContext(); var context = GetContext();
@@ -181,7 +230,7 @@ namespace UnitTests.AspNetCore.Extensions
public void ShouldReturnFalseOnRemote() public void ShouldReturnFalseOnRemote()
{ {
// arrange // arrange
remote = IPAddress.Parse("1.2.3.4"); _remote = IPAddress.Parse("1.2.3.4");
var context = GetContext(); var context = GetContext();
@@ -196,9 +245,9 @@ namespace UnitTests.AspNetCore.Extensions
public void ShouldReturnTrueOnDefaultHeader() public void ShouldReturnTrueOnDefaultHeader()
{ {
// arrange // arrange
remote = IPAddress.Parse("1.2.3.4"); _remote = IPAddress.Parse("1.2.3.4");
var headerIp = IPAddress.Loopback; var headerIp = IPAddress.Loopback;
requestHeaders.Add("X-Forwarded-For", headerIp.ToString()); _requestHeaders.Add("X-Forwarded-For", headerIp.ToString());
var context = GetContext(); var context = GetContext();
@@ -213,15 +262,15 @@ namespace UnitTests.AspNetCore.Extensions
public void ShouldReturnTrueOnCustomHeader() public void ShouldReturnTrueOnCustomHeader()
{ {
// arrange // arrange
remote = IPAddress.Parse("1.2.3.4"); _remote = IPAddress.Parse("1.2.3.4");
string headerName = "FooBar"; string headerName = "FooBar";
var headerIp = IPAddress.Loopback; var headerIp = IPAddress.Loopback;
requestHeaders.Add(headerName, headerIp.ToString()); _requestHeaders.Add(headerName, headerIp.ToString());
var context = GetContext(); var context = GetContext();
// act // act
bool result = context.IsLocalRequest(headerName: headerName); bool result = context.IsLocalRequest(ipHeaderName: headerName);
// assert // assert
Assert.IsTrue(result); Assert.IsTrue(result);
@@ -232,7 +281,7 @@ namespace UnitTests.AspNetCore.Extensions
{ {
// arrange // arrange
var headerIp = IPAddress.Parse("1.2.3.4"); var headerIp = IPAddress.Parse("1.2.3.4");
requestHeaders.Add("X-Forwarded-For", headerIp.ToString()); _requestHeaders.Add("X-Forwarded-For", headerIp.ToString());
var context = GetContext(); var context = GetContext();
@@ -249,12 +298,12 @@ namespace UnitTests.AspNetCore.Extensions
// arrange // arrange
string headerName = "FooBar"; string headerName = "FooBar";
var headerIp = IPAddress.Parse("1.2.3.4"); var headerIp = IPAddress.Parse("1.2.3.4");
requestHeaders.Add(headerName, headerIp.ToString()); _requestHeaders.Add(headerName, headerIp.ToString());
var context = GetContext(); var context = GetContext();
// act // act
bool result = context.IsLocalRequest(headerName: headerName); bool result = context.IsLocalRequest(ipHeaderName: headerName);
// assert // assert
Assert.IsFalse(result); Assert.IsFalse(result);
@@ -284,8 +333,8 @@ namespace UnitTests.AspNetCore.Extensions
string request = "abc"; string request = "abc";
string query = "def"; string query = "def";
items.Add("OriginalRequest", request); _items.Add("OriginalRequest", request);
requestQueries.Add("ReturnUrl", query); _requestQueries.Add("ReturnUrl", query);
var context = GetContext(); var context = GetContext();
@@ -302,7 +351,7 @@ namespace UnitTests.AspNetCore.Extensions
{ {
// arrange // arrange
string query = "def"; string query = "def";
requestQueries.Add("ReturnUrl", query); _requestQueries.Add("ReturnUrl", query);
var context = GetContext(); var context = GetContext();
@@ -327,7 +376,7 @@ namespace UnitTests.AspNetCore.Extensions
context.ClearSession(); context.ClearSession();
// assert // assert
sessionMock.Verify(s => s.Clear(), Times.Once); _sessionMock.Verify(s => s.Clear(), Times.Once);
} }
[TestMethod] [TestMethod]
@@ -340,7 +389,7 @@ namespace UnitTests.AspNetCore.Extensions
context.ClearSession(); context.ClearSession();
// assert // assert
sessionMock.Verify(s => s.Clear(), Times.Never); _sessionMock.Verify(s => s.Clear(), Times.Never);
} }
#endregion Session #endregion Session
@@ -349,7 +398,7 @@ namespace UnitTests.AspNetCore.Extensions
{ {
// Request // Request
var requestHeaderMock = new Mock<IHeaderDictionary>(); var requestHeaderMock = new Mock<IHeaderDictionary>();
foreach (var header in requestHeaders) foreach (var header in _requestHeaders)
{ {
requestHeaderMock requestHeaderMock
.Setup(h => h.ContainsKey(header.Key)) .Setup(h => h.ContainsKey(header.Key))
@@ -360,7 +409,7 @@ namespace UnitTests.AspNetCore.Extensions
} }
var requestQueryMock = new Mock<IQueryCollection>(); var requestQueryMock = new Mock<IQueryCollection>();
foreach (var query in requestQueries) foreach (var query in _requestQueries)
{ {
requestQueryMock requestQueryMock
.Setup(h => h.ContainsKey(query.Key)) .Setup(h => h.ContainsKey(query.Key))
@@ -385,7 +434,7 @@ namespace UnitTests.AspNetCore.Extensions
var antiforgeryMock = new Mock<IAntiforgery>(); var antiforgeryMock = new Mock<IAntiforgery>();
antiforgeryMock antiforgeryMock
.Setup(af => af.GetAndStoreTokens(It.IsAny<HttpContext>())) .Setup(af => af.GetAndStoreTokens(It.IsAny<HttpContext>()))
.Returns(string.IsNullOrWhiteSpace(tokenName) ? null : new AntiforgeryTokenSet(tokenValue, tokenValue, tokenName, tokenName)); .Returns(() => string.IsNullOrWhiteSpace(_tokenValue) ? null : new AntiforgeryTokenSet(_tokenValue, _tokenValue, _tokenFormName, _tokenHeaderName));
requestServicesMock requestServicesMock
.Setup(rs => rs.GetService(typeof(IAntiforgery))) .Setup(rs => rs.GetService(typeof(IAntiforgery)))
@@ -399,10 +448,10 @@ namespace UnitTests.AspNetCore.Extensions
.Returns(IPAddress.Loopback); .Returns(IPAddress.Loopback);
connectionInfoMock connectionInfoMock
.Setup(ci => ci.RemoteIpAddress) .Setup(ci => ci.RemoteIpAddress)
.Returns(remote); .Returns(_remote);
// Session // Session
sessionMock = new Mock<ISession>(); _sessionMock = new Mock<ISession>();
var contextMock = new Mock<HttpContext>(); var contextMock = new Mock<HttpContext>();
contextMock contextMock
@@ -416,12 +465,12 @@ namespace UnitTests.AspNetCore.Extensions
.Returns(connectionInfoMock.Object); .Returns(connectionInfoMock.Object);
contextMock contextMock
.Setup(c => c.Items) .Setup(c => c.Items)
.Returns(items); .Returns(_items);
if (hasSession) if (hasSession)
{ {
contextMock contextMock
.Setup(c => c.Session) .Setup(c => c.Session)
.Returns(sessionMock.Object); .Returns(_sessionMock.Object);
} }
return contextMock.Object; return contextMock.Object;

View File

@@ -9,12 +9,12 @@ namespace UnitTests.AspNetCore.Extensions
[System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage]
public class ModelStateDictionaryExtensionsTests public class ModelStateDictionaryExtensionsTests
{ {
private TestModel testModel; private TestModel _testModel;
[TestInitialize] [TestInitialize]
public void InitializeTests() public void InitializeTests()
{ {
testModel = new TestModel _testModel = new TestModel
{ {
ValueA = "A", ValueA = "A",
ValueB = "B", ValueB = "B",
@@ -33,7 +33,7 @@ namespace UnitTests.AspNetCore.Extensions
var modelState = new ModelStateDictionary(); var modelState = new ModelStateDictionary();
// act // act
modelState.AddModelError(testModel, m => m.ValueA, "ShitHappens"); modelState.AddModelError(_testModel, m => m.ValueA, "ShitHappens");
// assert // assert
Assert.AreEqual(1, modelState.Count); Assert.AreEqual(1, modelState.Count);
@@ -48,7 +48,7 @@ namespace UnitTests.AspNetCore.Extensions
var modelState = new ModelStateDictionary(); var modelState = new ModelStateDictionary();
// act // act
modelState.AddModelError(testModel, m => m.SubModel.SubValueB, "ShitHappens"); modelState.AddModelError(_testModel, m => m.SubModel.SubValueB, "ShitHappens");
// assert // assert
Assert.AreEqual(1, modelState.Count); Assert.AreEqual(1, modelState.Count);
@@ -64,7 +64,7 @@ namespace UnitTests.AspNetCore.Extensions
ModelStateDictionary modelState = null; ModelStateDictionary modelState = null;
// act // act
modelState.AddModelError(testModel, m => m.SubModel.SubValueB, "ShitHappens"); modelState.AddModelError(_testModel, m => m.SubModel.SubValueB, "ShitHappens");
} }
[TestMethod] [TestMethod]
@@ -75,7 +75,7 @@ namespace UnitTests.AspNetCore.Extensions
var modelState = new ModelStateDictionary(); var modelState = new ModelStateDictionary();
// act // act
modelState.AddModelError(testModel, m => m, "ShitHappens"); modelState.AddModelError(_testModel, m => m, "ShitHappens");
} }
internal class TestModel internal class TestModel

View File

@@ -10,12 +10,12 @@ namespace UnitTests.AspNetCore.Extensions
[System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage]
public class SessionExtensionsTests public class SessionExtensionsTests
{ {
private Mock<ISession> sessionMock; private Mock<ISession> _sessionMock;
private string sessionKey; private string _sessionKey;
private byte[] sessionValue; private byte[] _sessionValue;
private TestModel model; private TestModel _model;
internal class TestModel internal class TestModel
{ {
@@ -27,10 +27,10 @@ namespace UnitTests.AspNetCore.Extensions
[TestInitialize] [TestInitialize]
public void InitializeTests() public void InitializeTests()
{ {
sessionKey = null; _sessionKey = null;
sessionValue = null; _sessionValue = null;
model = new TestModel _model = new TestModel
{ {
ValueA = "A", ValueA = "A",
ValueB = "B" ValueB = "B"
@@ -41,7 +41,7 @@ namespace UnitTests.AspNetCore.Extensions
public void ShouldCheckKeyExists() public void ShouldCheckKeyExists()
{ {
// arrange // arrange
sessionKey = "exists"; _sessionKey = "exists";
var session = GetSession(); var session = GetSession();
// act // act
@@ -57,25 +57,25 @@ namespace UnitTests.AspNetCore.Extensions
public void ShouldGetValue() public void ShouldGetValue()
{ {
// arrange // arrange
sessionKey = "test"; _sessionKey = "test";
sessionValue = Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(model)); _sessionValue = Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(_model));
var session = GetSession(); var session = GetSession();
// act // act
var result = session.GetValue<TestModel>(sessionKey); var result = session.GetValue<TestModel>(_sessionKey);
// assert // assert
Assert.IsNotNull(result); Assert.IsNotNull(result);
Assert.AreEqual(model.ValueA, result.ValueA); Assert.AreEqual(_model.ValueA, result.ValueA);
Assert.AreEqual(model.ValueB, result.ValueB); Assert.AreEqual(_model.ValueB, result.ValueB);
} }
[TestMethod] [TestMethod]
public void ShouldGetNull() public void ShouldGetNull()
{ {
// arrange // arrange
sessionKey = "foo"; _sessionKey = "foo";
sessionValue = Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(model)); _sessionValue = Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(_model));
var session = GetSession(); var session = GetSession();
// act // act
@@ -89,25 +89,25 @@ namespace UnitTests.AspNetCore.Extensions
public void ShouldGetValueWithFallback() public void ShouldGetValueWithFallback()
{ {
// arrange // arrange
sessionKey = "test"; _sessionKey = "test";
sessionValue = Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(model)); _sessionValue = Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(_model));
var session = GetSession(); var session = GetSession();
// act // act
var result = session.GetValue(sessionKey, new TestModel()); var result = session.GetValue(_sessionKey, new TestModel());
// assert // assert
Assert.IsNotNull(result); Assert.IsNotNull(result);
Assert.AreEqual(model.ValueA, result.ValueA); Assert.AreEqual(_model.ValueA, result.ValueA);
Assert.AreEqual(model.ValueB, result.ValueB); Assert.AreEqual(_model.ValueB, result.ValueB);
} }
[TestMethod] [TestMethod]
public void ShouldGetFallback() public void ShouldGetFallback()
{ {
// arrange // arrange
sessionKey = "foo"; _sessionKey = "foo";
sessionValue = Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(model)); _sessionValue = Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(_model));
var session = GetSession(); var session = GetSession();
// act // act
@@ -127,33 +127,33 @@ namespace UnitTests.AspNetCore.Extensions
var session = GetSession(); var session = GetSession();
// act // act
session.SetValue(key, model); session.SetValue(key, _model);
// arrange // arrange
Assert.AreEqual(key, sessionKey); Assert.AreEqual(key, _sessionKey);
Assert.AreEqual(JsonConvert.SerializeObject(model), Encoding.UTF8.GetString(sessionValue)); Assert.AreEqual(JsonConvert.SerializeObject(_model), Encoding.UTF8.GetString(_sessionValue));
} }
private ISession GetSession() private ISession GetSession()
{ {
string[] keys = new[] { sessionKey }; string[] keys = [_sessionKey];
sessionMock = new Mock<ISession>(); _sessionMock = new Mock<ISession>();
sessionMock _sessionMock
.Setup(s => s.TryGetValue(It.IsAny<string>(), out sessionValue)) .Setup(s => s.TryGetValue(It.IsAny<string>(), out _sessionValue))
.Returns<string, byte[]>((key, value) => sessionKey == key); .Returns<string, byte[]>((key, value) => _sessionKey == key);
sessionMock _sessionMock
.Setup(s => s.Set(It.IsAny<string>(), It.IsAny<byte[]>())) .Setup(s => s.Set(It.IsAny<string>(), It.IsAny<byte[]>()))
.Callback<string, byte[]>((key, value) => .Callback<string, byte[]>((key, value) =>
{ {
sessionKey = key; _sessionKey = key;
sessionValue = value; _sessionValue = value;
}); });
sessionMock _sessionMock
.Setup(s => s.Keys) .Setup(s => s.Keys)
.Returns(keys); .Returns(keys);
return sessionMock.Object; return _sessionMock.Object;
} }
} }
} }

View File

@@ -6,38 +6,38 @@ using System.Security.Claims;
using System.Text; using System.Text;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using AMWD.Common.AspNetCore.BasicAuthentication; using AMWD.Common.AspNetCore.Security.BasicAuthentication;
using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Primitives; using Microsoft.Extensions.Primitives;
using Microsoft.VisualStudio.TestTools.UnitTesting; using Microsoft.VisualStudio.TestTools.UnitTesting;
using Moq; using Moq;
namespace UnitTests.AspNetCore.BasicAuthentication namespace UnitTests.AspNetCore.Security.BasicAuthentication
{ {
[TestClass] [TestClass]
[System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage]
public class BasicAuthenticationMiddlewareTests public class BasicAuthenticationMiddlewareTests
{ {
private Dictionary<string, string> requestHeaders; private Dictionary<string, string> _requestHeaders;
private Dictionary<string, string> responseHeadersCallback; private Dictionary<string, string> _responseHeadersCallback;
private int responseStatusCodeCallback; private int _responseStatusCodeCallback;
private string validatorRealm; private string _validatorRealm;
private ClaimsPrincipal validatorResponse; private ClaimsPrincipal _validatorResponse;
private List<(string username, string password, IPAddress ipAddr)> validatorCallback; private List<(string username, string password, IPAddress ipAddr)> _validatorCallback;
[TestInitialize] [TestInitialize]
public void InitializeTests() public void InitializeTests()
{ {
requestHeaders = new Dictionary<string, string>(); _requestHeaders = [];
responseHeadersCallback = new Dictionary<string, string>(); _responseHeadersCallback = [];
responseStatusCodeCallback = 0; _responseStatusCodeCallback = 0;
validatorRealm = null; _validatorRealm = null;
validatorResponse = null; _validatorResponse = null;
validatorCallback = new List<(string username, string password, IPAddress ipAddr)>(); _validatorCallback = [];
} }
[TestMethod] [TestMethod]
@@ -47,8 +47,8 @@ namespace UnitTests.AspNetCore.BasicAuthentication
string username = "user"; string username = "user";
string password = "pass:word"; string password = "pass:word";
requestHeaders.Add("Authorization", $"Basic {Convert.ToBase64String(Encoding.UTF8.GetBytes($"{username}:{password}"))}"); _requestHeaders.Add("Authorization", $"Basic {Convert.ToBase64String(Encoding.UTF8.GetBytes($"{username}:{password}"))}");
validatorResponse = new ClaimsPrincipal(); _validatorResponse = new ClaimsPrincipal();
var middleware = GetMiddleware(); var middleware = GetMiddleware();
var context = GetContext(); var context = GetContext();
@@ -57,13 +57,13 @@ namespace UnitTests.AspNetCore.BasicAuthentication
await middleware.InvokeAsync(context); await middleware.InvokeAsync(context);
// assert // assert
Assert.AreEqual(0, responseStatusCodeCallback); // not triggered Assert.AreEqual(0, _responseStatusCodeCallback); // not triggered
Assert.AreEqual(0, responseHeadersCallback.Count); Assert.AreEqual(0, _responseHeadersCallback.Count);
Assert.AreEqual(1, validatorCallback.Count); Assert.AreEqual(1, _validatorCallback.Count);
Assert.AreEqual(username, validatorCallback.First().username); Assert.AreEqual(username, _validatorCallback.First().username);
Assert.AreEqual(password, validatorCallback.First().password); Assert.AreEqual(password, _validatorCallback.First().password);
Assert.AreEqual(IPAddress.Loopback, validatorCallback.First().ipAddr); Assert.AreEqual(IPAddress.Loopback, _validatorCallback.First().ipAddr);
} }
[TestMethod] [TestMethod]
@@ -77,13 +77,13 @@ namespace UnitTests.AspNetCore.BasicAuthentication
await middleware.InvokeAsync(context); await middleware.InvokeAsync(context);
// assert // assert
Assert.AreEqual(401, responseStatusCodeCallback); Assert.AreEqual(401, _responseStatusCodeCallback);
Assert.AreEqual(0, validatorCallback.Count); Assert.AreEqual(0, _validatorCallback.Count);
Assert.AreEqual(1, responseHeadersCallback.Count); Assert.AreEqual(1, _responseHeadersCallback.Count);
Assert.AreEqual("WWW-Authenticate", responseHeadersCallback.Keys.First()); Assert.AreEqual("WWW-Authenticate", _responseHeadersCallback.Keys.First());
Assert.AreEqual("Basic", responseHeadersCallback.Values.First()); Assert.AreEqual("Basic", _responseHeadersCallback.Values.First());
} }
[TestMethod] [TestMethod]
@@ -93,10 +93,10 @@ namespace UnitTests.AspNetCore.BasicAuthentication
string username = "user"; string username = "user";
string password = "pw"; string password = "pw";
validatorRealm = "TEST"; _validatorRealm = "TEST";
var remote = IPAddress.Parse("1.2.3.4"); var remote = IPAddress.Parse("1.2.3.4");
requestHeaders.Add("Authorization", $"Basic {Convert.ToBase64String(Encoding.UTF8.GetBytes($"{username}:{password}"))}"); _requestHeaders.Add("Authorization", $"Basic {Convert.ToBase64String(Encoding.UTF8.GetBytes($"{username}:{password}"))}");
var middleware = GetMiddleware(); var middleware = GetMiddleware();
var context = GetContext(remote); var context = GetContext(remote);
@@ -105,16 +105,16 @@ namespace UnitTests.AspNetCore.BasicAuthentication
await middleware.InvokeAsync(context); await middleware.InvokeAsync(context);
// assert // assert
Assert.AreEqual(401, responseStatusCodeCallback); Assert.AreEqual(401, _responseStatusCodeCallback);
Assert.AreEqual(1, responseHeadersCallback.Count); Assert.AreEqual(1, _responseHeadersCallback.Count);
Assert.AreEqual("WWW-Authenticate", responseHeadersCallback.Keys.First()); Assert.AreEqual("WWW-Authenticate", _responseHeadersCallback.Keys.First());
Assert.AreEqual($"Basic realm=\"{validatorRealm}\"", responseHeadersCallback.Values.First()); Assert.AreEqual($"Basic realm=\"{_validatorRealm}\"", _responseHeadersCallback.Values.First());
Assert.AreEqual(1, validatorCallback.Count); Assert.AreEqual(1, _validatorCallback.Count);
Assert.AreEqual(username, validatorCallback.First().username); Assert.AreEqual(username, _validatorCallback.First().username);
Assert.AreEqual(password, validatorCallback.First().password); Assert.AreEqual(password, _validatorCallback.First().password);
Assert.AreEqual(remote, validatorCallback.First().ipAddr); Assert.AreEqual(remote, _validatorCallback.First().ipAddr);
} }
[TestMethod] [TestMethod]
@@ -123,7 +123,7 @@ namespace UnitTests.AspNetCore.BasicAuthentication
// arrange // arrange
string username = "user"; string username = "user";
requestHeaders.Add("Authorization", $"Basic {Convert.ToBase64String(Encoding.UTF8.GetBytes($"{username}"))}"); _requestHeaders.Add("Authorization", $"Basic {Convert.ToBase64String(Encoding.UTF8.GetBytes($"{username}"))}");
var middleware = GetMiddleware(); var middleware = GetMiddleware();
var context = GetContext(); var context = GetContext();
@@ -132,7 +132,7 @@ namespace UnitTests.AspNetCore.BasicAuthentication
await middleware.InvokeAsync(context); await middleware.InvokeAsync(context);
// assert // assert
Assert.AreEqual(500, responseStatusCodeCallback); Assert.AreEqual(500, _responseStatusCodeCallback);
} }
private BasicAuthenticationMiddleware GetMiddleware() private BasicAuthenticationMiddleware GetMiddleware()
@@ -141,11 +141,11 @@ namespace UnitTests.AspNetCore.BasicAuthentication
var validatorMock = new Mock<IBasicAuthenticationValidator>(); var validatorMock = new Mock<IBasicAuthenticationValidator>();
validatorMock validatorMock
.Setup(v => v.Realm) .Setup(v => v.Realm)
.Returns(validatorRealm); .Returns(_validatorRealm);
validatorMock validatorMock
.Setup(v => v.ValidateAsync(It.IsAny<string>(), It.IsAny<string>(), It.IsAny<IPAddress>(), It.IsAny<CancellationToken>())) .Setup(v => v.ValidateAsync(It.IsAny<string>(), It.IsAny<string>(), It.IsAny<IPAddress>(), It.IsAny<CancellationToken>()))
.Callback<string, string, IPAddress, CancellationToken>((username, password, ipAddress, _) => validatorCallback.Add((username, password, ipAddress))) .Callback<string, string, IPAddress, CancellationToken>((username, password, ipAddress, _) => _validatorCallback.Add((username, password, ipAddress)))
.ReturnsAsync(validatorResponse); .ReturnsAsync(_validatorResponse);
return new BasicAuthenticationMiddleware(nextMock.Object, validatorMock.Object); return new BasicAuthenticationMiddleware(nextMock.Object, validatorMock.Object);
} }
@@ -154,7 +154,7 @@ namespace UnitTests.AspNetCore.BasicAuthentication
{ {
// Request // Request
var requestHeaderMock = new Mock<IHeaderDictionary>(); var requestHeaderMock = new Mock<IHeaderDictionary>();
foreach (var header in requestHeaders) foreach (var header in _requestHeaders)
{ {
requestHeaderMock requestHeaderMock
.Setup(h => h.ContainsKey(header.Key)) .Setup(h => h.ContainsKey(header.Key))
@@ -173,7 +173,7 @@ namespace UnitTests.AspNetCore.BasicAuthentication
var responseHeaderMock = new Mock<IHeaderDictionary>(); var responseHeaderMock = new Mock<IHeaderDictionary>();
responseHeaderMock responseHeaderMock
.SetupSet(h => h[It.IsAny<string>()] = It.IsAny<StringValues>()) .SetupSet(h => h[It.IsAny<string>()] = It.IsAny<StringValues>())
.Callback<string, StringValues>((key, value) => responseHeadersCallback[key] = value); .Callback<string, StringValues>((key, value) => _responseHeadersCallback[key] = value);
var responseMock = new Mock<HttpResponse>(); var responseMock = new Mock<HttpResponse>();
responseMock responseMock
@@ -181,7 +181,7 @@ namespace UnitTests.AspNetCore.BasicAuthentication
.Returns(responseHeaderMock.Object); .Returns(responseHeaderMock.Object);
responseMock responseMock
.SetupSet(r => r.StatusCode = It.IsAny<int>()) .SetupSet(r => r.StatusCode = It.IsAny<int>())
.Callback<int>((code) => responseStatusCodeCallback = code); .Callback<int>((code) => _responseStatusCodeCallback = code);
// Connection // Connection
var connectionInfoMock = new Mock<ConnectionInfo>(); var connectionInfoMock = new Mock<ConnectionInfo>();

View File

@@ -0,0 +1,141 @@
using System;
using System.Security.Claims;
using System.Threading.Tasks;
using AMWD.Common.AspNetCore.Security.PathProtection;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Moq;
namespace UnitTests.AspNetCore.Security.PathProtection
{
[TestClass]
[System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage]
public class ProtectedPathMiddlewareTests
{
private Mock<RequestDelegate> _nextMock;
private Mock<HttpContext> _httpContextMock;
private Mock<IAuthorizationService> _authorizationServiceMock;
private Mock<IAuthenticationService> _authenticationServiceMock;
private ProtectedPathOptions _options;
[TestInitialize]
public void InitializeTest()
{
_options = new ProtectedPathOptions
{
Path = "/secure/protected",
PolicyName = "Protection"
};
}
[TestMethod]
public async Task ShouldValidateAccessSuccessful()
{
// arrange
var middleware = GetMiddleware();
var context = GetHttpContext(_options.Path);
var auth = GetAuthService(true);
// act
await middleware.InvokeAsync(context, auth);
// assert
_authorizationServiceMock.Verify(s => s.AuthorizeAsync(It.IsAny<ClaimsPrincipal>(), It.IsAny<object>(), _options.PolicyName), Times.Once);
_authorizationServiceMock.VerifyNoOtherCalls();
_authenticationServiceMock.Verify(s => s.ChallengeAsync(It.IsAny<HttpContext>(), It.IsAny<string>(), It.IsAny<AuthenticationProperties>()), Times.Never);
_authenticationServiceMock.VerifyNoOtherCalls();
_nextMock.Verify(n => n.Invoke(It.IsAny<HttpContext>()), Times.Once);
_nextMock.VerifyNoOtherCalls();
}
[TestMethod]
public async Task ShouldNotValidate()
{
// arrange
var middleware = GetMiddleware();
var context = GetHttpContext("/some/path");
var auth = GetAuthService(true);
// act
await middleware.InvokeAsync(context, auth);
// assert
_authorizationServiceMock.Verify(s => s.AuthorizeAsync(It.IsAny<ClaimsPrincipal>(), It.IsAny<object>(), _options.PolicyName), Times.Never);
_authorizationServiceMock.VerifyNoOtherCalls();
_authenticationServiceMock.Verify(s => s.ChallengeAsync(It.IsAny<HttpContext>(), It.IsAny<string>(), It.IsAny<AuthenticationProperties>()), Times.Never);
_authenticationServiceMock.VerifyNoOtherCalls();
_nextMock.Verify(n => n.Invoke(It.IsAny<HttpContext>()), Times.Once);
_nextMock.VerifyNoOtherCalls();
}
[TestMethod]
public async Task ShouldValidateAccessFailure()
{
// arrange
var middleware = GetMiddleware();
var context = GetHttpContext(_options.Path);
var auth = GetAuthService(false);
// act
await middleware.InvokeAsync(context, auth);
// assert
_authorizationServiceMock.Verify(s => s.AuthorizeAsync(It.IsAny<ClaimsPrincipal>(), It.IsAny<object>(), _options.PolicyName), Times.Once);
_authorizationServiceMock.VerifyNoOtherCalls();
_authenticationServiceMock.Verify(s => s.ChallengeAsync(It.IsAny<HttpContext>(), It.IsAny<string>(), It.IsAny<AuthenticationProperties>()), Times.Once);
_authenticationServiceMock.VerifyNoOtherCalls();
_nextMock.Verify(n => n.Invoke(It.IsAny<HttpContext>()), Times.Never);
_nextMock.VerifyNoOtherCalls();
}
private ProtectedPathMiddleware GetMiddleware()
{
_nextMock = new Mock<RequestDelegate>();
return new ProtectedPathMiddleware(_nextMock.Object, _options);
}
private HttpContext GetHttpContext(string requestPath)
{
var requestMock = new Mock<HttpRequest>();
requestMock
.Setup(r => r.Path)
.Returns(new PathString(requestPath));
_authenticationServiceMock = new Mock<IAuthenticationService>();
var requestServicesMock = new Mock<IServiceProvider>();
requestServicesMock
.Setup(s => s.GetService(typeof(IAuthenticationService)))
.Returns(_authenticationServiceMock.Object);
_httpContextMock = new Mock<HttpContext>();
_httpContextMock
.Setup(c => c.Request)
.Returns(requestMock.Object);
_httpContextMock
.Setup(c => c.RequestServices)
.Returns(requestServicesMock.Object);
return _httpContextMock.Object;
}
private IAuthorizationService GetAuthService(bool success)
{
_authorizationServiceMock = new Mock<IAuthorizationService>();
_authorizationServiceMock
.Setup(service => service.AuthorizeAsync(It.IsAny<ClaimsPrincipal>(), It.IsAny<object>(), It.IsAny<string>()))
.ReturnsAsync(() => success ? AuthorizationResult.Success() : AuthorizationResult.Failed());
return _authorizationServiceMock.Object;
}
}
}

View File

@@ -1,9 +1,4 @@
using System; using System.Reflection;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Identity; using Microsoft.AspNetCore.Identity;
using Microsoft.VisualStudio.TestTools.UnitTesting; using Microsoft.VisualStudio.TestTools.UnitTesting;

View File

@@ -0,0 +1,306 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using AMWD.Common.Cli;
using Microsoft.VisualStudio.TestTools.UnitTesting;
namespace UnitTests.Common.Cli
{
[TestClass]
public class CommandLineParserTests
{
[TestMethod]
public void ShouldParseStringToArgs()
{
// arrange
// act
string[] result = CommandLineParser.ParseArgsString("Option1 \"Option 2\" \"Some \"\" Option\" Foo=Bar \\ /help \\\\backslash \\\"escapedquote \\test");
// assert
Assert.IsNotNull(result);
Assert.AreEqual(9, result.Length);
Assert.AreEqual("Option1", result[0]);
Assert.AreEqual("Option 2", result[1]);
Assert.AreEqual("Some \" Option", result[2]);
Assert.AreEqual("Foo=Bar", result[3]);
Assert.AreEqual("\\", result[4]);
Assert.AreEqual("/help", result[5]);
Assert.AreEqual("\\backslash", result[6]);
Assert.AreEqual("\"escapedquote", result[7]);
Assert.AreEqual("\\test", result[8]);
}
[TestMethod]
public void ShouldReadArgs()
{
// arrange
var parser = new CommandLineParser();
// act
parser.ReadArgs("Option1 \"Option 2\"");
string[] args = parser.GetType().GetField("_args", BindingFlags.NonPublic | BindingFlags.Instance).GetValue(parser) as string[];
// assert
Assert.IsNotNull(args);
Assert.AreEqual(2, args.Length);
Assert.AreEqual("Option1", args[0]);
Assert.AreEqual("Option 2", args[1]);
}
[TestMethod]
public void ShouldRegisterOptions()
{
// arrange
var parser = new CommandLineParser();
// act
parser.RegisterOption("opt1");
parser.RegisterOption("opt2", 1);
parser.RegisterOption("opt3", 2).Required().Single();
parser.RegisterOption("opt4").Alias("option4");
var options = parser.GetType().GetField("_options", BindingFlags.NonPublic | BindingFlags.Instance).GetValue(parser) as List<Option>;
// assert
Assert.IsNotNull(options);
Assert.AreEqual(4, options.Count);
Assert.AreEqual(1, options.ElementAt(0).Names.Count);
Assert.AreEqual("opt1", options.ElementAt(0).Names.First());
Assert.AreEqual(0, options.ElementAt(0).ParameterCount);
Assert.IsFalse(options.ElementAt(0).IsSingle);
Assert.IsFalse(options.ElementAt(0).IsRequired);
Assert.AreEqual(1, options.ElementAt(1).Names.Count);
Assert.AreEqual("opt2", options.ElementAt(1).Names.First());
Assert.AreEqual(1, options.ElementAt(1).ParameterCount);
Assert.IsFalse(options.ElementAt(1).IsSingle);
Assert.IsFalse(options.ElementAt(1).IsRequired);
Assert.AreEqual(1, options.ElementAt(2).Names.Count);
Assert.AreEqual("opt3", options.ElementAt(2).Names.First());
Assert.AreEqual(2, options.ElementAt(2).ParameterCount);
Assert.IsTrue(options.ElementAt(2).IsSingle);
Assert.IsTrue(options.ElementAt(2).IsRequired);
Assert.AreEqual(2, options.ElementAt(3).Names.Count);
Assert.AreEqual("opt4", options.ElementAt(3).Names.First());
Assert.AreEqual("option4", options.ElementAt(3).Names.Last());
Assert.AreEqual(0, options.ElementAt(3).ParameterCount);
Assert.IsFalse(options.ElementAt(3).IsSingle);
Assert.IsFalse(options.ElementAt(3).IsRequired);
}
[TestMethod]
public void ShouldParse()
{
// arrange
string argString = "/opt1 /opt2:two -opt3=three1 three2 --opt4:four /test:done -- foo bar";
var parser = new CommandLineParser();
parser.RegisterOption("opt1");
parser.RegisterOption("opt2", 1);
parser.RegisterOption("opt3", 2);
parser.RegisterOption("opt4", 1);
var opt = parser.RegisterOption("notUsed");
parser.RegisterOption("testing", 1);
parser.ReadArgs(argString);
// act
parser.Parse();
// assert
Assert.IsFalse(opt.IsSet);
Assert.AreEqual(7, parser.Arguments.Length);
Assert.AreEqual(2, parser.FreeArguments.Length);
Assert.AreEqual("foo", parser.FreeArguments.First());
Assert.AreEqual("bar", parser.FreeArguments.Last());
Assert.IsTrue(parser.Arguments.ElementAt(0).Option.IsSet);
Assert.IsNull(parser.Arguments.ElementAt(0).Option.Value);
Assert.AreEqual(0, parser.Arguments.ElementAt(0).Values.Length);
Assert.IsTrue(parser.Arguments.ElementAt(1).Option.IsSet);
Assert.AreEqual("two", parser.Arguments.ElementAt(1).Option.Value);
Assert.AreEqual(1, parser.Arguments.ElementAt(1).Values.Length);
Assert.AreEqual("two", parser.Arguments.ElementAt(1).Values.First());
Assert.IsTrue(parser.Arguments.ElementAt(2).Option.IsSet);
Assert.AreEqual("three1", parser.Arguments.ElementAt(2).Option.Value);
Assert.AreEqual(2, parser.Arguments.ElementAt(2).Values.Length);
Assert.AreEqual("three1", parser.Arguments.ElementAt(2).Values.First());
Assert.AreEqual("three2", parser.Arguments.ElementAt(2).Values.Last());
Assert.IsTrue(parser.Arguments.ElementAt(3).Option.IsSet);
Assert.AreEqual("four", parser.Arguments.ElementAt(3).Option.Value);
Assert.AreEqual(1, parser.Arguments.ElementAt(3).Values.Length);
Assert.AreEqual("four", parser.Arguments.ElementAt(3).Values.First());
Assert.IsTrue(parser.Arguments.ElementAt(4).Option.IsSet);
Assert.AreEqual("testing", parser.Arguments.ElementAt(4).Option.Names.First());
Assert.AreEqual("done", parser.Arguments.ElementAt(4).Option.Value);
Assert.AreEqual(1, parser.Arguments.ElementAt(4).Values.Length);
Assert.AreEqual("done", parser.Arguments.ElementAt(4).Values.First());
Assert.IsNull(parser.Arguments.ElementAt(5).Option);
Assert.AreEqual("foo", parser.Arguments.ElementAt(5).Value);
Assert.AreEqual(1, parser.Arguments.ElementAt(5).Values.Length);
Assert.IsNull(parser.Arguments.ElementAt(6).Option);
Assert.AreEqual("bar", parser.Arguments.ElementAt(6).Value);
Assert.AreEqual(1, parser.Arguments.ElementAt(6).Values.Length);
}
[TestMethod]
public void ShouldExecuteOptionActionOnParse()
{
// arrange
Argument actionArgument = null;
string[] args = ["/run", "--opt"];
var parser = new CommandLineParser();
parser.RegisterOption("opt").Required();
parser.RegisterOption("run").Do(arg => actionArgument = arg);
// act
parser.Parse(args);
// assert
Assert.IsNotNull(actionArgument);
Assert.IsNotNull(actionArgument.Option);
Assert.AreEqual("run", actionArgument.Option.Names.First());
}
[TestMethod]
public void ShouldReturnSetOptions()
{
// arrange
string argString = "/Opt1 --opt3";
var parser = new CommandLineParser();
parser.ReadArgs(argString);
parser.RegisterOption("opt1");
parser.RegisterOption("opt2");
parser.RegisterOption("opt3");
// act
var opts = parser.SetOptions;
// assert
Assert.AreEqual(2, opts.Length);
Assert.AreEqual("opt1", opts.First().Names.First());
Assert.AreEqual("opt3", opts.Last().Names.First());
}
[TestMethod]
[ExpectedException(typeof(ArgumentNullException))]
public void ShouldThrowExceptionOnNullArgs()
{
string[] args = null;
var parser = new CommandLineParser();
// act
parser.Parse(args);
// assert - ArgumentNullException
Assert.Fail();
}
[TestMethod]
[ExpectedException(typeof(Exception))]
public void ShouldThrowExceptionOnMultipleAutocomplete()
{
// arrange
string[] args = ["/Opt:on"];
var parser = new CommandLineParser
{
IsCaseSensitive = true
};
parser.RegisterOption("Option1", 1);
parser.RegisterOption("Option2", 1);
// act
parser.Parse(args);
// assert - Exception
Assert.Fail();
}
[TestMethod]
[ExpectedException(typeof(Exception))]
public void ShouldThrowExceptionOnMissingOption()
{
// arrange
string[] args = ["/Option:on"];
var parser = new CommandLineParser
{
AutoCompleteOptions = false
};
parser.RegisterOption("Opt1", 1);
parser.RegisterOption("Opt2", 1);
// act
parser.Parse(args);
// assert - Exception
Assert.Fail();
}
[TestMethod]
[ExpectedException(typeof(Exception))]
public void ShouldTrhowExceptionOnDuplicateOption()
{
// arrange
string[] args = ["/Opt:on", "--opt=off"];
var parser = new CommandLineParser();
parser.RegisterOption("opt", 1).Single();
// act
parser.Parse(args);
// assert - Exception
Assert.Fail();
}
[TestMethod]
[ExpectedException(typeof(Exception))]
public void ShouldThrowExceptionOnMissingArgument()
{
// arrange
string[] args = ["/Option"];
var parser = new CommandLineParser();
parser.RegisterOption("option", 1);
// act
parser.Parse(args);
// assert - Exception
Assert.Fail();
}
[TestMethod]
[ExpectedException(typeof(Exception))]
public void ShouldThrowExceptionForMissingRequiredOption()
{
// arrange
string[] args = ["/opt"];
var parser = new CommandLineParser();
parser.RegisterOption("opt").Required();
parser.RegisterOption("foo").Required();
// act
parser.Parse(args);
// assert - Exception
Assert.Fail();
}
}
}

View File

@@ -0,0 +1,90 @@
using System;
using System.Collections.Generic;
using AMWD.Common.Cli;
using Microsoft.VisualStudio.TestTools.UnitTesting;
namespace UnitTests.Common.Cli
{
[TestClass]
public class EnumerableWalkerTests
{
private List<string> _list;
[TestInitialize]
public void Initialize()
{
_list = ["one", "two", "three", "four"];
}
[TestMethod]
[ExpectedException(typeof(ArgumentNullException))]
public void ShouldThrowExceptionOnNullReference()
{
// arrange
// act
_ = new EnumerableWalker<object>(null);
// assert - ArgumentNullException
Assert.Fail();
}
[TestMethod]
public void ShouldReturnEnumerator()
{
// arrange
var walker = new EnumerableWalker<string>(_list);
// act
var enumerator = walker.GetEnumerator();
// assert
Assert.IsNotNull(enumerator);
}
[TestMethod]
public void ShouldReturnGenericEnumerator()
{
// arrange
var walker = new EnumerableWalker<string>(_list);
// act
var enumerator = ((IEnumerable<string>)walker).GetEnumerator();
// assert
Assert.IsNotNull(enumerator);
}
[TestMethod]
public void ShouldReturnItems()
{
// arrange
var walker = new EnumerableWalker<string>(_list);
_ = walker.GetEnumerator();
string[] items = new string[_list.Count];
// act
for (int i = 0; i < _list.Count; i++)
items[i] = walker.GetNext();
// assert
for (int i = 0; i < _list.Count; i++)
Assert.AreEqual(_list[i], items[i], $"Position {i} failed");
}
[TestMethod]
public void ShouldReturnDefaultWhenNothingLeft()
{
// arrange
var walker = new EnumerableWalker<string>(Array.Empty<string>());
_ = walker.GetEnumerator();
// act
string item = walker.GetNext();
// assert
Assert.AreEqual(default, item);
}
}
}

View File

@@ -12,7 +12,7 @@ namespace UnitTests.Common.Extensions
{ {
// arrange // arrange
string str = "Hello World!"; string str = "Hello World!";
byte[] bytes = new byte[] { 0xaf, 0xfe }; byte[] bytes = [0xaf, 0xfe];
// act // act
string strHash = str.Md5(); string strHash = str.Md5();
@@ -28,7 +28,7 @@ namespace UnitTests.Common.Extensions
{ {
// arrange // arrange
string str = "Hello World!"; string str = "Hello World!";
byte[] bytes = new byte[] { 0xaf, 0xfe }; byte[] bytes = [0xaf, 0xfe];
// act // act
string strHash = str.Sha1(); string strHash = str.Sha1();
@@ -44,7 +44,7 @@ namespace UnitTests.Common.Extensions
{ {
// arrange // arrange
string str = "Hello World!"; string str = "Hello World!";
byte[] bytes = new byte[] { 0xaf, 0xfe }; byte[] bytes = [0xaf, 0xfe];
// act // act
string strHash = str.Sha256(); string strHash = str.Sha256();
@@ -60,7 +60,7 @@ namespace UnitTests.Common.Extensions
{ {
// arrange // arrange
string str = "Hello World!"; string str = "Hello World!";
byte[] bytes = new byte[] { 0xaf, 0xfe }; byte[] bytes = [0xaf, 0xfe];
// act // act
string strHash = str.Sha512(); string strHash = str.Sha512();

View File

@@ -56,11 +56,11 @@ namespace UnitTests.Common.Extensions
// arrange // arrange
var innerExceptions = new List<Exception> var innerExceptions = new List<Exception>
{ {
new Exception("Inner Exception 1."), new("Inner Exception 1."),
new Exception("Inner Exception 2. See the inner exception for details.", new Exception("Inner Exception of Exception 2.")), new("Inner Exception 2. See the inner exception for details.", new Exception("Inner Exception of Exception 2.")),
new Exception("Inner Exception 3."), new("Inner Exception 3."),
new Exception("Inner Exception 4."), new("Inner Exception 4."),
new Exception("Inner Exception 5.") new("Inner Exception 5.")
}; };
var aggregateException = new AggregateException("Lots of exceptions.", innerExceptions); var aggregateException = new AggregateException("Lots of exceptions.", innerExceptions);
string expectedMessage = "Inner Exception 1. Inner Exception 2. Inner Exception of Exception 2. Inner Exception 3."; string expectedMessage = "Inner Exception 1. Inner Exception 2. Inner Exception of Exception 2. Inner Exception 3.";

View File

@@ -224,7 +224,7 @@ namespace UnitTests.Common.Extensions
public void ShouldConvertToJArray() public void ShouldConvertToJArray()
{ {
// arrange // arrange
string[] stringArray = new[] { "one", "two", "three" }; string[] stringArray = ["one", "two", "three"];
var objectArray = new[] var objectArray = new[]
{ {
new JsonTestClass { StringValue = "One" }, new JsonTestClass { StringValue = "One" },
@@ -260,7 +260,7 @@ namespace UnitTests.Common.Extensions
// act // act
string topLevelString = jObj.GetValue<string>("stringValue"); string topLevelString = jObj.GetValue<string>("stringValue");
decimal topLevelDecimal = jObj.GetValue<decimal>("decimalValue"); decimal topLevelDecimal = jObj.GetValue<decimal>("decimalValue");
int subLevelInteger = jObj.GetValue<int>("object:integerValue"); int subLevelInteger = jObj.GetValue<int>("object:IntegerValue");
string subLevelString = jObj.GetValue<string>("object:stringValue"); string subLevelString = jObj.GetValue<string>("object:stringValue");
string notExistingOnTopLevel = jObj.GetValue<string>("fancyValue"); string notExistingOnTopLevel = jObj.GetValue<string>("fancyValue");
@@ -288,7 +288,7 @@ namespace UnitTests.Common.Extensions
// act // act
string topLevelString = jObj.GetValue("stringValue", "Test String"); string topLevelString = jObj.GetValue("stringValue", "Test String");
decimal topLevelDecimal = jObj.GetValue("decimalValue", 13.24m); decimal topLevelDecimal = jObj.GetValue("decimalValue", 13.24m);
int subLevelInteger = jObj.GetValue("object:integerValue", 55); int subLevelInteger = jObj.GetValue("object:IntegerValue", 55);
string subLevelString = jObj.GetValue("object:stringValue", "Yeah!"); string subLevelString = jObj.GetValue("object:stringValue", "Yeah!");
string notExistingOnTopLevel = jObj.GetValue("fancyValue", "Party!"); string notExistingOnTopLevel = jObj.GetValue("fancyValue", "Party!");

View File

@@ -154,7 +154,7 @@ namespace UnitTests.Common.Extensions
catch (Exception) catch (Exception)
{ /* keep it quiet */ } { /* keep it quiet */ }
}); });
Task.WaitAll(awaitableTask); awaitableTask.Wait();
// assert // assert
Assert.IsTrue(isTimeout); Assert.IsTrue(isTimeout);
@@ -183,7 +183,7 @@ namespace UnitTests.Common.Extensions
catch (Exception) catch (Exception)
{ /* keep it quiet */ } { /* keep it quiet */ }
}); });
Task.WaitAll(awaitableTask); awaitableTask.Wait();
// assert // assert
Assert.IsTrue(isTimeout); Assert.IsTrue(isTimeout);
@@ -212,7 +212,7 @@ namespace UnitTests.Common.Extensions
catch (Exception) catch (Exception)
{ /* keep it quiet */ } { /* keep it quiet */ }
}); });
Task.WaitAll(awaitableTask); awaitableTask.Wait();
// assert // assert
Assert.IsTrue(isTimeout); Assert.IsTrue(isTimeout);

View File

@@ -87,7 +87,7 @@ namespace UnitTests.Common.Extensions
{ {
// arrange // arrange
byte[] bytes1 = null; byte[] bytes1 = null;
byte[] bytes2 = Array.Empty<byte>(); byte[] bytes2 = [];
// act // act
string hex1 = bytes1.BytesToHex(); string hex1 = bytes1.BytesToHex();
@@ -102,7 +102,7 @@ namespace UnitTests.Common.Extensions
public void ShouldReturnHexString() public void ShouldReturnHexString()
{ {
// arrange // arrange
byte[] bytes = new byte[] { 0xaf, 0xfe }; byte[] bytes = [0xaf, 0xfe];
// act // act
string hex = bytes.BytesToHex(); string hex = bytes.BytesToHex();
@@ -116,7 +116,7 @@ namespace UnitTests.Common.Extensions
public void ShouldReturnHexStringWithDelimiter() public void ShouldReturnHexStringWithDelimiter()
{ {
// arrange // arrange
byte[] bytes = new byte[] { 0xde, 0xad, 0xbe, 0xef }; byte[] bytes = [0xde, 0xad, 0xbe, 0xef];
// act // act
string hex = bytes.BytesToHex("_"); string hex = bytes.BytesToHex("_");

View File

@@ -15,14 +15,14 @@ namespace UnitTests.Common.Logging
[TestClass] [TestClass]
public class FileLoggerTests public class FileLoggerTests
{ {
private Mock<StreamWriter> streamWriterMock; private Mock<StreamWriter> _streamWriterMock;
private List<string> lines; private List<string> _lines;
[TestInitialize] [TestInitialize]
public void Initialize() public void Initialize()
{ {
lines = new List<string>(); _lines = [];
} }
[TestMethod] [TestMethod]
@@ -157,14 +157,14 @@ namespace UnitTests.Common.Logging
// act // act
logger.Log(LogLevel.Information, "Test Message"); logger.Log(LogLevel.Information, "Test Message");
SpinWait.SpinUntil(() => lines.Count == 1); SpinWait.SpinUntil(() => _lines.Count == 1);
// assert // assert
Assert.AreEqual("INFO | Test Message", lines.First()); Assert.AreEqual("INFO | Test Message", _lines.First());
streamWriterMock.Verify(sw => sw.WriteLineAsync(It.IsAny<string>()), Times.Once); _streamWriterMock.Verify(sw => sw.WriteLineAsync(It.IsAny<string>()), Times.Once);
streamWriterMock.Verify(sw => sw.FlushAsync(), Times.Once); _streamWriterMock.Verify(sw => sw.FlushAsync(), Times.Once);
streamWriterMock.VerifyNoOtherCalls(); _streamWriterMock.VerifyNoOtherCalls();
} }
[TestMethod] [TestMethod]
@@ -177,18 +177,18 @@ namespace UnitTests.Common.Logging
foreach (LogLevel level in Enum.GetValues<LogLevel>()) foreach (LogLevel level in Enum.GetValues<LogLevel>())
logger.Log(level, "Test Message"); logger.Log(level, "Test Message");
SpinWait.SpinUntil(() => lines.Count == 7); SpinWait.SpinUntil(() => _lines.Count == 7);
// assert // assert
streamWriterMock.Verify(sw => sw.WriteLineAsync("TRCE | Test Message"), Times.Once); _streamWriterMock.Verify(sw => sw.WriteLineAsync("TRCE | Test Message"), Times.Once);
streamWriterMock.Verify(sw => sw.WriteLineAsync("DBUG | Test Message"), Times.Once); _streamWriterMock.Verify(sw => sw.WriteLineAsync("DBUG | Test Message"), Times.Once);
streamWriterMock.Verify(sw => sw.WriteLineAsync("INFO | Test Message"), Times.Once); _streamWriterMock.Verify(sw => sw.WriteLineAsync("INFO | Test Message"), Times.Once);
streamWriterMock.Verify(sw => sw.WriteLineAsync("WARN | Test Message"), Times.Once); _streamWriterMock.Verify(sw => sw.WriteLineAsync("WARN | Test Message"), Times.Once);
streamWriterMock.Verify(sw => sw.WriteLineAsync("FAIL | Test Message"), Times.Once); _streamWriterMock.Verify(sw => sw.WriteLineAsync("FAIL | Test Message"), Times.Once);
streamWriterMock.Verify(sw => sw.WriteLineAsync("CRIT | Test Message"), Times.Once); _streamWriterMock.Verify(sw => sw.WriteLineAsync("CRIT | Test Message"), Times.Once);
streamWriterMock.Verify(sw => sw.WriteLineAsync(" | Test Message"), Times.Once); _streamWriterMock.Verify(sw => sw.WriteLineAsync(" | Test Message"), Times.Once);
streamWriterMock.Verify(sw => sw.FlushAsync(), Times.AtLeastOnce); _streamWriterMock.Verify(sw => sw.FlushAsync(), Times.AtLeastOnce);
streamWriterMock.VerifyNoOtherCalls(); _streamWriterMock.VerifyNoOtherCalls();
} }
[TestMethod] [TestMethod]
@@ -202,14 +202,14 @@ namespace UnitTests.Common.Logging
foreach (LogLevel level in Enum.GetValues<LogLevel>()) foreach (LogLevel level in Enum.GetValues<LogLevel>())
logger.Log(level, "Test Message"); logger.Log(level, "Test Message");
SpinWait.SpinUntil(() => lines.Count == 3); SpinWait.SpinUntil(() => _lines.Count == 3);
// assert // assert
streamWriterMock.Verify(sw => sw.WriteLineAsync("FAIL | Test Message"), Times.Once); _streamWriterMock.Verify(sw => sw.WriteLineAsync("FAIL | Test Message"), Times.Once);
streamWriterMock.Verify(sw => sw.WriteLineAsync("CRIT | Test Message"), Times.Once); _streamWriterMock.Verify(sw => sw.WriteLineAsync("CRIT | Test Message"), Times.Once);
streamWriterMock.Verify(sw => sw.WriteLineAsync(" | Test Message"), Times.Once); _streamWriterMock.Verify(sw => sw.WriteLineAsync(" | Test Message"), Times.Once);
streamWriterMock.Verify(sw => sw.FlushAsync(), Times.AtLeastOnce); _streamWriterMock.Verify(sw => sw.FlushAsync(), Times.AtLeastOnce);
streamWriterMock.VerifyNoOtherCalls(); _streamWriterMock.VerifyNoOtherCalls();
} }
[TestMethod] [TestMethod]
@@ -222,13 +222,13 @@ namespace UnitTests.Common.Logging
// act // act
logger.LogWarning("Some Warning"); logger.LogWarning("Some Warning");
SpinWait.SpinUntil(() => lines.Count == 1); SpinWait.SpinUntil(() => _lines.Count == 1);
// assert // assert
streamWriterMock.Verify(sw => sw.WriteLineAsync($"{DateTime.Now:yyyy-MM-dd HH:mm} | WARN | Some Warning"), Times.Once); _streamWriterMock.Verify(sw => sw.WriteLineAsync($"{DateTime.Now:yyyy-MM-dd HH:mm} | WARN | Some Warning"), Times.Once);
streamWriterMock.Verify(sw => sw.FlushAsync(), Times.Once); _streamWriterMock.Verify(sw => sw.FlushAsync(), Times.Once);
streamWriterMock.VerifyNoOtherCalls(); _streamWriterMock.VerifyNoOtherCalls();
} }
[TestMethod] [TestMethod]
@@ -241,12 +241,12 @@ namespace UnitTests.Common.Logging
// act // act
logger.LogWarning("Some Warning"); logger.LogWarning("Some Warning");
SpinWait.SpinUntil(() => lines.Count == 1); SpinWait.SpinUntil(() => _lines.Count == 1);
// assert // assert
streamWriterMock.Verify(sw => sw.WriteLineAsync($"{DateTime.UtcNow:yyyy-MM-dd HH:mm} | WARN | Some Warning"), Times.Once); _streamWriterMock.Verify(sw => sw.WriteLineAsync($"{DateTime.UtcNow:yyyy-MM-dd HH:mm} | WARN | Some Warning"), Times.Once);
streamWriterMock.Verify(sw => sw.FlushAsync(), Times.Once); _streamWriterMock.Verify(sw => sw.FlushAsync(), Times.Once);
streamWriterMock.VerifyNoOtherCalls(); _streamWriterMock.VerifyNoOtherCalls();
} }
[TestMethod] [TestMethod]
@@ -264,12 +264,12 @@ namespace UnitTests.Common.Logging
// act // act
logger.LogWarning("Some Warning"); logger.LogWarning("Some Warning");
SpinWait.SpinUntil(() => lines.Count == 1); SpinWait.SpinUntil(() => _lines.Count == 1);
// assert // assert
streamWriterMock.Verify(sw => sw.WriteLineAsync($"{DateTime.Now:yyyy-MM-dd HH:mm} | WARN | [NamedInstance] Some Warning"), Times.Once); _streamWriterMock.Verify(sw => sw.WriteLineAsync($"{DateTime.Now:yyyy-MM-dd HH:mm} | WARN | [NamedInstance] Some Warning"), Times.Once);
streamWriterMock.Verify(sw => sw.FlushAsync(), Times.Once); _streamWriterMock.Verify(sw => sw.FlushAsync(), Times.Once);
streamWriterMock.VerifyNoOtherCalls(); _streamWriterMock.VerifyNoOtherCalls();
Assert.AreEqual(0, new FileInfo(file).Length); Assert.AreEqual(0, new FileInfo(file).Length);
} }
@@ -290,13 +290,13 @@ namespace UnitTests.Common.Logging
using (var scope = logger.BeginScope("scope")) using (var scope = logger.BeginScope("scope"))
{ {
logger.LogError("Test"); logger.LogError("Test");
SpinWait.SpinUntil(() => lines.Count == 1); SpinWait.SpinUntil(() => _lines.Count == 1);
} }
// assert // assert
streamWriterMock.Verify(sw => sw.WriteLineAsync("FAIL | [NamedInstance] Test"), Times.Once); _streamWriterMock.Verify(sw => sw.WriteLineAsync("FAIL | [NamedInstance] Test"), Times.Once);
streamWriterMock.Verify(sw => sw.FlushAsync(), Times.Once); _streamWriterMock.Verify(sw => sw.FlushAsync(), Times.Once);
streamWriterMock.VerifyNoOtherCalls(); _streamWriterMock.VerifyNoOtherCalls();
scopeProvider.Verify(sp => sp.Push("scope"), Times.Once); scopeProvider.Verify(sp => sp.Push("scope"), Times.Once);
scopeProvider.Verify(sp => sp.ForEachScope(It.IsAny<Action<object, It.IsAnyType>>(), It.IsAny<It.IsAnyType>()), Times.Once); scopeProvider.Verify(sp => sp.ForEachScope(It.IsAny<Action<object, It.IsAnyType>>(), It.IsAny<It.IsAnyType>()), Times.Once);
@@ -311,12 +311,12 @@ namespace UnitTests.Common.Logging
// act // act
logger.LogCritical(new Exception("TestException"), ""); logger.LogCritical(new Exception("TestException"), "");
SpinWait.SpinUntil(() => lines.Count == 1); SpinWait.SpinUntil(() => _lines.Count == 1);
// assert // assert
streamWriterMock.Verify(sw => sw.WriteLineAsync("CRIT | System.Exception: TestException"), Times.Once); _streamWriterMock.Verify(sw => sw.WriteLineAsync("CRIT | System.Exception: TestException"), Times.Once);
streamWriterMock.Verify(sw => sw.FlushAsync(), Times.Once); _streamWriterMock.Verify(sw => sw.FlushAsync(), Times.Once);
streamWriterMock.VerifyNoOtherCalls(); _streamWriterMock.VerifyNoOtherCalls();
} }
[TestMethod] [TestMethod]
@@ -327,12 +327,12 @@ namespace UnitTests.Common.Logging
// act // act
logger.LogCritical(new Exception("TestException"), "Bad things happen..."); logger.LogCritical(new Exception("TestException"), "Bad things happen...");
SpinWait.SpinUntil(() => lines.Count == 1); SpinWait.SpinUntil(() => _lines.Count == 1);
// assert // assert
streamWriterMock.Verify(sw => sw.WriteLineAsync($"CRIT | Bad things happen...{Environment.NewLine} System.Exception: TestException"), Times.Once); _streamWriterMock.Verify(sw => sw.WriteLineAsync($"CRIT | Bad things happen...{Environment.NewLine} System.Exception: TestException"), Times.Once);
streamWriterMock.Verify(sw => sw.FlushAsync(), Times.Once); _streamWriterMock.Verify(sw => sw.FlushAsync(), Times.Once);
streamWriterMock.VerifyNoOtherCalls(); _streamWriterMock.VerifyNoOtherCalls();
} }
private FileLogger GetFileLogger(string name = null, IExternalScopeProvider scopeProvider = null) private FileLogger GetFileLogger(string name = null, IExternalScopeProvider scopeProvider = null)
@@ -340,10 +340,10 @@ namespace UnitTests.Common.Logging
string tmpFilePath = Path.GetTempFileName(); string tmpFilePath = Path.GetTempFileName();
try try
{ {
streamWriterMock = new Mock<StreamWriter>(Stream.Null); _streamWriterMock = new Mock<StreamWriter>(Stream.Null);
streamWriterMock _streamWriterMock
.Setup(sw => sw.WriteLineAsync(It.IsAny<string>())) .Setup(sw => sw.WriteLineAsync(It.IsAny<string>()))
.Callback<string>(line => lines.Add(line)) .Callback<string>(line => _lines.Add(line))
.Returns(Task.CompletedTask); .Returns(Task.CompletedTask);
FileLogger fileLogger; FileLogger fileLogger;
@@ -356,9 +356,9 @@ namespace UnitTests.Common.Logging
fileLogger = new FileLogger(tmpFilePath, name, scopeProvider); fileLogger = new FileLogger(tmpFilePath, name, scopeProvider);
} }
var fieldInfo = fileLogger.GetType().GetField("fileWriter", BindingFlags.NonPublic | BindingFlags.Instance); var fieldInfo = fileLogger.GetType().GetField("_fileWriter", BindingFlags.NonPublic | BindingFlags.Instance);
(fieldInfo.GetValue(fileLogger) as StreamWriter).Dispose(); (fieldInfo.GetValue(fileLogger) as StreamWriter).Dispose();
fieldInfo.SetValue(fileLogger, streamWriterMock.Object); fieldInfo.SetValue(fileLogger, _streamWriterMock.Object);
return fileLogger; return fileLogger;
} }

View File

@@ -10,35 +10,35 @@ namespace UnitTests.Common.Utilities
[System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage]
public class AsyncQueueTests public class AsyncQueueTests
{ {
private Queue<TestElement> internalQueue; private Queue<TestElement> _internalQueue;
private TestElement queueElement1; private TestElement _queueElement1;
private TestElement queueElement2; private TestElement _queueElement2;
private TestElement queueElement3; private TestElement _queueElement3;
[TestInitialize] [TestInitialize]
public void InitializeTest() public void InitializeTest()
{ {
queueElement1 = new TestElement _queueElement1 = new TestElement
{ {
Number = 111, Number = 111,
Text = "one" Text = "one"
}; };
queueElement2 = new TestElement _queueElement2 = new TestElement
{ {
Number = 222, Number = 222,
Text = "two" Text = "two"
}; };
queueElement3 = new TestElement _queueElement3 = new TestElement
{ {
Number = 333, Number = 333,
Text = "three" Text = "three"
}; };
internalQueue = new Queue<TestElement>(); _internalQueue = new Queue<TestElement>();
internalQueue.Enqueue(queueElement1); _internalQueue.Enqueue(_queueElement1);
internalQueue.Enqueue(queueElement2); _internalQueue.Enqueue(_queueElement2);
internalQueue.Enqueue(queueElement3); _internalQueue.Enqueue(_queueElement3);
} }
[TestMethod] [TestMethod]
@@ -47,15 +47,15 @@ namespace UnitTests.Common.Utilities
// arrange // arrange
var element = new TestElement { Number = 1, Text = "Hello" }; var element = new TestElement { Number = 1, Text = "Hello" };
internalQueue.Clear(); _internalQueue.Clear();
var queue = GetQueue(); var queue = GetQueue();
// act // act
queue.Enqueue(element); queue.Enqueue(element);
// assert // assert
Assert.AreEqual(1, internalQueue.Count); Assert.AreEqual(1, _internalQueue.Count);
Assert.AreEqual(internalQueue.Count, queue.Count); Assert.AreEqual(_internalQueue.Count, queue.Count);
} }
[TestMethod] [TestMethod]
@@ -65,7 +65,7 @@ namespace UnitTests.Common.Utilities
var element = new TestElement { Number = 1, Text = "Hello" }; var element = new TestElement { Number = 1, Text = "Hello" };
bool available = false; bool available = false;
internalQueue.Clear(); _internalQueue.Clear();
var queue = GetQueue(); var queue = GetQueue();
// act // act
@@ -88,7 +88,7 @@ namespace UnitTests.Common.Utilities
var element = new TestElement { Number = 1, Text = "Hello" }; var element = new TestElement { Number = 1, Text = "Hello" };
TestElement callback = null; TestElement callback = null;
internalQueue.Clear(); _internalQueue.Clear();
var queue = GetQueue(); var queue = GetQueue();
// act // act
@@ -110,18 +110,18 @@ namespace UnitTests.Common.Utilities
// arrange // arrange
var elements = new TestElement[] var elements = new TestElement[]
{ {
new TestElement { Number = 1, Text = "Hello" }, new() { Number = 1, Text = "Hello" },
new TestElement { Number = 2, Text = "World" }, new() { Number = 2, Text = "World" },
}; };
internalQueue.Clear(); _internalQueue.Clear();
var queue = GetQueue(); var queue = GetQueue();
// act // act
queue.Enqueue(elements); queue.Enqueue(elements);
// assert // assert
Assert.AreEqual(2, internalQueue.Count); Assert.AreEqual(2, _internalQueue.Count);
Assert.AreEqual(queue.Count, internalQueue.Count); Assert.AreEqual(queue.Count, _internalQueue.Count);
} }
[TestMethod] [TestMethod]
@@ -136,7 +136,7 @@ namespace UnitTests.Common.Utilities
// assert // assert
Assert.IsTrue(isSuccess); Assert.IsTrue(isSuccess);
Assert.IsNotNull(item); Assert.IsNotNull(item);
Assert.AreEqual(queueElement1, item); Assert.AreEqual(_queueElement1, item);
Assert.AreEqual(3, queue.Count); Assert.AreEqual(3, queue.Count);
} }
@@ -144,7 +144,7 @@ namespace UnitTests.Common.Utilities
public void ShouldNotPeekAValue() public void ShouldNotPeekAValue()
{ {
// arrange // arrange
internalQueue.Clear(); _internalQueue.Clear();
var queue = GetQueue(); var queue = GetQueue();
// act // act
@@ -167,7 +167,7 @@ namespace UnitTests.Common.Utilities
// assert // assert
Assert.IsTrue(isSuccess); Assert.IsTrue(isSuccess);
Assert.IsNotNull(item); Assert.IsNotNull(item);
Assert.AreEqual(queueElement1, item); Assert.AreEqual(_queueElement1, item);
Assert.AreEqual(2, queue.Count); Assert.AreEqual(2, queue.Count);
} }
@@ -175,7 +175,7 @@ namespace UnitTests.Common.Utilities
public void ShouldNotDequeueAValue() public void ShouldNotDequeueAValue()
{ {
// arrange // arrange
internalQueue.Clear(); _internalQueue.Clear();
var queue = GetQueue(); var queue = GetQueue();
// act // act
@@ -193,14 +193,14 @@ namespace UnitTests.Common.Utilities
var queue = GetQueue(); var queue = GetQueue();
// act // act
queue.Remove(queueElement2); queue.Remove(_queueElement2);
var item1 = queue.Dequeue(); var item1 = queue.Dequeue();
var item2 = queue.Dequeue(); var item2 = queue.Dequeue();
// assert // assert
Assert.AreEqual(0, queue.Count); Assert.AreEqual(0, queue.Count);
Assert.AreEqual(queueElement1, item1); Assert.AreEqual(_queueElement1, item1);
Assert.AreEqual(queueElement3, item2); Assert.AreEqual(_queueElement3, item2);
} }
[TestMethod] [TestMethod]
@@ -217,22 +217,22 @@ namespace UnitTests.Common.Utilities
// assert // assert
Assert.AreEqual(0, queue.Count); Assert.AreEqual(0, queue.Count);
Assert.AreEqual(queueElement1, item1); Assert.AreEqual(_queueElement1, item1);
Assert.AreEqual(queueElement2, item2); Assert.AreEqual(_queueElement2, item2);
Assert.AreEqual(queueElement3, item3); Assert.AreEqual(_queueElement3, item3);
} }
[TestMethod] [TestMethod]
public async Task ShouldAwaitOneDequeue() public async Task ShouldAwaitOneDequeue()
{ {
// arrange // arrange
internalQueue.Clear(); _internalQueue.Clear();
var queue = GetQueue(); var queue = GetQueue();
var task = Task.Run(async () => var task = Task.Run(async () =>
{ {
await Task.Delay(1000); await Task.Delay(1000);
queue.Enqueue(new[] { queueElement1, queueElement2, queueElement3 }); queue.Enqueue(new[] { _queueElement1, _queueElement2, _queueElement3 });
}); });
// act // act
@@ -241,20 +241,20 @@ namespace UnitTests.Common.Utilities
// assert // assert
Assert.AreEqual(2, queue.Count); Assert.AreEqual(2, queue.Count);
Assert.IsNotNull(item); Assert.IsNotNull(item);
Assert.AreEqual(queueElement1, item); Assert.AreEqual(_queueElement1, item);
} }
[TestMethod] [TestMethod]
public async Task ShouldAwaitManyDequeue() public async Task ShouldAwaitManyDequeue()
{ {
// arrange // arrange
internalQueue.Clear(); _internalQueue.Clear();
var queue = GetQueue(); var queue = GetQueue();
var task = Task.Run(async () => var task = Task.Run(async () =>
{ {
await Task.Delay(1000); await Task.Delay(1000);
queue.Enqueue(new[] { queueElement1, queueElement2, queueElement3 }); queue.Enqueue(new[] { _queueElement1, _queueElement2, _queueElement3 });
}); });
// act // act
@@ -264,21 +264,21 @@ namespace UnitTests.Common.Utilities
Assert.AreEqual(1, queue.Count); Assert.AreEqual(1, queue.Count);
Assert.IsNotNull(items); Assert.IsNotNull(items);
Assert.AreEqual(2, items.Length); Assert.AreEqual(2, items.Length);
Assert.AreEqual(queueElement1, items[0]); Assert.AreEqual(_queueElement1, items[0]);
Assert.AreEqual(queueElement2, items[1]); Assert.AreEqual(_queueElement2, items[1]);
} }
[TestMethod] [TestMethod]
public async Task ShouldAwaitAllDequeue() public async Task ShouldAwaitAllDequeue()
{ {
// arrange // arrange
internalQueue.Clear(); _internalQueue.Clear();
var queue = GetQueue(); var queue = GetQueue();
var task = Task.Run(async () => var task = Task.Run(async () =>
{ {
await Task.Delay(1000); await Task.Delay(1000);
queue.Enqueue(new[] { queueElement1, queueElement2, queueElement3 }); queue.Enqueue(new[] { _queueElement1, _queueElement2, _queueElement3 });
}); });
// act // act
@@ -288,22 +288,22 @@ namespace UnitTests.Common.Utilities
Assert.AreEqual(0, queue.Count); Assert.AreEqual(0, queue.Count);
Assert.IsNotNull(items); Assert.IsNotNull(items);
Assert.AreEqual(3, items.Length); Assert.AreEqual(3, items.Length);
Assert.AreEqual(queueElement1, items[0]); Assert.AreEqual(_queueElement1, items[0]);
Assert.AreEqual(queueElement2, items[1]); Assert.AreEqual(_queueElement2, items[1]);
Assert.AreEqual(queueElement3, items[2]); Assert.AreEqual(_queueElement3, items[2]);
} }
[TestMethod] [TestMethod]
public async Task ShouldAwaitAvailableDequeue() public async Task ShouldAwaitAvailableDequeue()
{ {
// arrange // arrange
internalQueue.Clear(); _internalQueue.Clear();
var queue = GetQueue(); var queue = GetQueue();
var task = Task.Run(async () => var task = Task.Run(async () =>
{ {
await Task.Delay(1000); await Task.Delay(1000);
queue.Enqueue(new[] { queueElement1, queueElement2, queueElement3 }); queue.Enqueue(new[] { _queueElement1, _queueElement2, _queueElement3 });
}); });
// act // act
@@ -313,8 +313,8 @@ namespace UnitTests.Common.Utilities
Assert.AreEqual(1, queue.Count); Assert.AreEqual(1, queue.Count);
Assert.IsNotNull(items); Assert.IsNotNull(items);
Assert.AreEqual(2, items.Length); Assert.AreEqual(2, items.Length);
Assert.AreEqual(queueElement1, items[0]); Assert.AreEqual(_queueElement1, items[0]);
Assert.AreEqual(queueElement2, items[1]); Assert.AreEqual(_queueElement2, items[1]);
} }
[TestMethod] [TestMethod]
@@ -322,7 +322,7 @@ namespace UnitTests.Common.Utilities
public async Task ShouldThrowArumentOutOfRangeException() public async Task ShouldThrowArumentOutOfRangeException()
{ {
// arrange // arrange
internalQueue.Clear(); _internalQueue.Clear();
var queue = GetQueue(); var queue = GetQueue();
// act // act
@@ -336,8 +336,8 @@ namespace UnitTests.Common.Utilities
{ {
var asyncQueue = new AsyncQueue<TestElement>(); var asyncQueue = new AsyncQueue<TestElement>();
var field = asyncQueue.GetType().GetField("queue", BindingFlags.Instance | BindingFlags.NonPublic); var field = asyncQueue.GetType().GetField("_queue", BindingFlags.Instance | BindingFlags.NonPublic);
field.SetValue(asyncQueue, internalQueue); field.SetValue(asyncQueue, _internalQueue);
return asyncQueue; return asyncQueue;
} }

View File

@@ -10,22 +10,22 @@ namespace UnitTests.Common.Utilities
{ {
[TestClass] [TestClass]
[System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage]
public class CryptographyHelperTests public partial class CryptographyHelperTests
{ {
private string keyFile; private string _keyFile;
private CryptographyHelper cryptoHelper; private CryptographyHelper _cryptoHelper;
[TestInitialize] [TestInitialize]
public void Initialize() public void Initialize()
{ {
keyFile = Path.GetTempFileName(); _keyFile = Path.GetTempFileName();
cryptoHelper = new CryptographyHelper(keyFile); _cryptoHelper = new CryptographyHelper(_keyFile);
} }
[TestCleanup] [TestCleanup]
public void Cleanup() public void Cleanup()
{ {
File.Delete(keyFile); File.Delete(_keyFile);
} }
#region Static #region Static
@@ -40,12 +40,12 @@ namespace UnitTests.Common.Utilities
// arrange // arrange
using var _ = CryptographyHelperSaltMock.Create(0); using var _ = CryptographyHelperSaltMock.Create(0);
byte[] bytes = new byte[] { 0xaf, 0xfe }; byte[] bytes = [0xaf, 0xfe];
string str = "ABC"; string str = "ABC";
string password1 = "P@ssw0rd!"; string password1 = "P@ssw0rd!";
string password2 = "P@ssw0rd"; string password2 = "P@ssw0rd";
byte[] expectedBytes = new byte[] { 0x7c, 0x7b, 0x77, 0x56, 0x91, 0x1a, 0xd9, 0xc0, 0x72, 0x70, 0x36, 0x88, 0x9f, 0xb4, 0xb5, 0xbc }; byte[] expectedBytes = [0x7c, 0x7b, 0x77, 0x56, 0x91, 0x1a, 0xd9, 0xc0, 0x72, 0x70, 0x36, 0x88, 0x9f, 0xb4, 0xb5, 0xbc];
// act // act
byte[] cipherBytes1 = CryptographyHelper.AesEncrypt(bytes, password1); byte[] cipherBytes1 = CryptographyHelper.AesEncrypt(bytes, password1);
@@ -68,12 +68,12 @@ namespace UnitTests.Common.Utilities
using var _ = CryptographyHelperSaltMock.Create(0); using var _ = CryptographyHelperSaltMock.Create(0);
string cipherStr = "ueLuhFNpCuYmx8v3hczHtg=="; string cipherStr = "ueLuhFNpCuYmx8v3hczHtg==";
byte[] cipherBytes = new byte[] { 0x7c, 0x7b, 0x77, 0x56, 0x91, 0x1a, 0xd9, 0xc0, 0x72, 0x70, 0x36, 0x88, 0x9f, 0xb4, 0xb5, 0xbc }; byte[] cipherBytes = [0x7c, 0x7b, 0x77, 0x56, 0x91, 0x1a, 0xd9, 0xc0, 0x72, 0x70, 0x36, 0x88, 0x9f, 0xb4, 0xb5, 0xbc];
string password1 = "P@ssw0rd!"; string password1 = "P@ssw0rd!";
string password2 = "P@ssw0rd"; string password2 = "P@ssw0rd";
byte[] expectedBytes = new byte[] { 0xaf, 0xfe }; byte[] expectedBytes = [0xaf, 0xfe];
// act // act
byte[] plainBytes1 = CryptographyHelper.AesDecrypt(cipherBytes, password1); byte[] plainBytes1 = CryptographyHelper.AesDecrypt(cipherBytes, password1);
@@ -104,7 +104,7 @@ namespace UnitTests.Common.Utilities
public void ShouldEncryptDecryptAesBytes() public void ShouldEncryptDecryptAesBytes()
{ {
// arrange // arrange
byte[] plain = new byte[] { 0xaf, 0xfe }; byte[] plain = [0xaf, 0xfe];
string password = "P@ssw0rd!"; string password = "P@ssw0rd!";
// act // act
@@ -154,12 +154,12 @@ namespace UnitTests.Common.Utilities
// arrange // arrange
using var _ = CryptographyHelperSaltMock.Create(0); using var _ = CryptographyHelperSaltMock.Create(0);
byte[] bytes = new byte[] { 0xaf, 0xfe }; byte[] bytes = [0xaf, 0xfe];
string str = "ABC"; string str = "ABC";
string password1 = "P@ssw0rd!"; string password1 = "P@ssw0rd!";
string password2 = "P@ssw0rd"; string password2 = "P@ssw0rd";
byte[] expectedBytes = new byte[] { 0xbf, 0x59, 0x1f, 0x48, 0x69, 0xab, 0x18, 0xc7 }; byte[] expectedBytes = [0xbf, 0x59, 0x1f, 0x48, 0x69, 0xab, 0x18, 0xc7];
// act // act
byte[] cipherBytes1 = CryptographyHelper.TripleDesEncrypt(bytes, password1); byte[] cipherBytes1 = CryptographyHelper.TripleDesEncrypt(bytes, password1);
@@ -182,12 +182,12 @@ namespace UnitTests.Common.Utilities
using var _ = CryptographyHelperSaltMock.Create(0); using var _ = CryptographyHelperSaltMock.Create(0);
string cipherStr = "1l74soBuuEI="; string cipherStr = "1l74soBuuEI=";
byte[] cipherBytes = new byte[] { 0xbf, 0x59, 0x1f, 0x48, 0x69, 0xab, 0x18, 0xc7 }; byte[] cipherBytes = [0xbf, 0x59, 0x1f, 0x48, 0x69, 0xab, 0x18, 0xc7];
string password1 = "P@ssw0rd!"; string password1 = "P@ssw0rd!";
string password2 = "P@ssw0rd"; string password2 = "P@ssw0rd";
byte[] expectedBytes = new byte[] { 0xaf, 0xfe }; byte[] expectedBytes = [0xaf, 0xfe];
// act // act
byte[] plainBytes1 = CryptographyHelper.TripleDesDecrypt(cipherBytes, password1); byte[] plainBytes1 = CryptographyHelper.TripleDesDecrypt(cipherBytes, password1);
@@ -218,7 +218,7 @@ namespace UnitTests.Common.Utilities
public void ShouldEncryptDecryptTdesBytes() public void ShouldEncryptDecryptTdesBytes()
{ {
// arrange // arrange
byte[] plain = new byte[] { 0xaf, 0xfe }; byte[] plain = [0xaf, 0xfe];
string password = "P@ssw0rd!"; string password = "P@ssw0rd!";
// act // act
@@ -269,7 +269,7 @@ namespace UnitTests.Common.Utilities
{ {
// arrange // arrange
string text = "Hello World!"; string text = "Hello World!";
byte[] bytes = new byte[] { 0xde, 0xad, 0xbe, 0xef }; byte[] bytes = [0xde, 0xad, 0xbe, 0xef];
string fileName = Path.GetTempFileName(); string fileName = Path.GetTempFileName();
// act // act
@@ -291,7 +291,7 @@ namespace UnitTests.Common.Utilities
{ {
// arrange // arrange
string text = "Hello World!"; string text = "Hello World!";
byte[] bytes = new byte[] { 0xde, 0xad, 0xbe, 0xef }; byte[] bytes = [0xde, 0xad, 0xbe, 0xef];
string fileName = Path.GetTempFileName(); string fileName = Path.GetTempFileName();
// act // act
@@ -313,7 +313,7 @@ namespace UnitTests.Common.Utilities
{ {
// arrange // arrange
string text = "Hello World!"; string text = "Hello World!";
byte[] bytes = new byte[] { 0xde, 0xad, 0xbe, 0xef }; byte[] bytes = [0xde, 0xad, 0xbe, 0xef];
string fileName = Path.GetTempFileName(); string fileName = Path.GetTempFileName();
// act // act
@@ -335,7 +335,7 @@ namespace UnitTests.Common.Utilities
{ {
// arrange // arrange
string text = "Hello World!"; string text = "Hello World!";
byte[] bytes = new byte[] { 0xde, 0xad, 0xbe, 0xef }; byte[] bytes = [0xde, 0xad, 0xbe, 0xef];
string fileName = Path.GetTempFileName(); string fileName = Path.GetTempFileName();
// act // act
@@ -415,8 +415,8 @@ namespace UnitTests.Common.Utilities
Assert.AreEqual(length, str1.Length); Assert.AreEqual(length, str1.Length);
Assert.AreEqual(length, str2.Length); Assert.AreEqual(length, str2.Length);
Assert.IsFalse(str1 == str2); Assert.IsFalse(str1 == str2);
Assert.IsFalse(Regex.IsMatch(str1, "[^0-9a-f]")); Assert.IsFalse(RandomStringWithPoolRegex().IsMatch(str1));
Assert.IsFalse(Regex.IsMatch(str2, "[^0-9a-f]")); Assert.IsFalse(RandomStringWithPoolRegex().IsMatch(str2));
} }
#endregion Random #endregion Random
@@ -537,7 +537,6 @@ namespace UnitTests.Common.Utilities
Assert.IsTrue(!string.IsNullOrWhiteSpace(content)); Assert.IsTrue(!string.IsNullOrWhiteSpace(content));
} }
[TestMethod] [TestMethod]
public void ShouldEncryptAesUsingKeyFile() public void ShouldEncryptAesUsingKeyFile()
{ {
@@ -545,11 +544,11 @@ namespace UnitTests.Common.Utilities
string str = "Hello World!"; string str = "Hello World!";
byte[] bytes = CryptographyHelper.GetRandomBytes(32); byte[] bytes = CryptographyHelper.GetRandomBytes(32);
string password = File.ReadAllText(keyFile); string password = File.ReadAllText(_keyFile);
// act // act
string cipherStr = cryptoHelper.EncryptAes(str); string cipherStr = _cryptoHelper.EncryptAes(str);
byte[] cipherBytes = cryptoHelper.EncryptAes(bytes); byte[] cipherBytes = _cryptoHelper.EncryptAes(bytes);
string plainStr = CryptographyHelper.AesDecrypt(cipherStr, password); string plainStr = CryptographyHelper.AesDecrypt(cipherStr, password);
byte[] plainBytes = CryptographyHelper.AesDecrypt(cipherBytes, password); byte[] plainBytes = CryptographyHelper.AesDecrypt(cipherBytes, password);
@@ -568,14 +567,14 @@ namespace UnitTests.Common.Utilities
string str = "Hello World!"; string str = "Hello World!";
byte[] bytes = CryptographyHelper.GetRandomBytes(32); byte[] bytes = CryptographyHelper.GetRandomBytes(32);
string password = File.ReadAllText(keyFile); string password = File.ReadAllText(_keyFile);
// act // act
string cipherStr = CryptographyHelper.AesEncrypt(str, password); string cipherStr = CryptographyHelper.AesEncrypt(str, password);
byte[] cipherBytes = CryptographyHelper.AesEncrypt(bytes, password); byte[] cipherBytes = CryptographyHelper.AesEncrypt(bytes, password);
string plainStr = cryptoHelper.DecryptAes(cipherStr); string plainStr = _cryptoHelper.DecryptAes(cipherStr);
byte[] plainBytes = cryptoHelper.DecryptAes(cipherBytes); byte[] plainBytes = _cryptoHelper.DecryptAes(cipherBytes);
// assert // assert
Assert.AreNotEqual(str, cipherStr); Assert.AreNotEqual(str, cipherStr);
@@ -591,11 +590,11 @@ namespace UnitTests.Common.Utilities
string str = "Hello World!"; string str = "Hello World!";
byte[] bytes = CryptographyHelper.GetRandomBytes(32); byte[] bytes = CryptographyHelper.GetRandomBytes(32);
string password = File.ReadAllText(keyFile); string password = File.ReadAllText(_keyFile);
// act // act
string cipherStr = cryptoHelper.EncryptTripleDes(str); string cipherStr = _cryptoHelper.EncryptTripleDes(str);
byte[] cipherBytes = cryptoHelper.EncryptTripleDes(bytes); byte[] cipherBytes = _cryptoHelper.EncryptTripleDes(bytes);
string plainStr = CryptographyHelper.TripleDesDecrypt(cipherStr, password); string plainStr = CryptographyHelper.TripleDesDecrypt(cipherStr, password);
byte[] plainBytes = CryptographyHelper.TripleDesDecrypt(cipherBytes, password); byte[] plainBytes = CryptographyHelper.TripleDesDecrypt(cipherBytes, password);
@@ -614,14 +613,14 @@ namespace UnitTests.Common.Utilities
string str = "Hello World!"; string str = "Hello World!";
byte[] bytes = CryptographyHelper.GetRandomBytes(32); byte[] bytes = CryptographyHelper.GetRandomBytes(32);
string password = File.ReadAllText(keyFile); string password = File.ReadAllText(_keyFile);
// act // act
string cipherStr = CryptographyHelper.TripleDesEncrypt(str, password); string cipherStr = CryptographyHelper.TripleDesEncrypt(str, password);
byte[] cipherBytes = CryptographyHelper.TripleDesEncrypt(bytes, password); byte[] cipherBytes = CryptographyHelper.TripleDesEncrypt(bytes, password);
string plainStr = cryptoHelper.DecryptTripleDes(cipherStr); string plainStr = _cryptoHelper.DecryptTripleDes(cipherStr);
byte[] plainBytes = cryptoHelper.DecryptTripleDes(cipherBytes); byte[] plainBytes = _cryptoHelper.DecryptTripleDes(cipherBytes);
// assert // assert
Assert.AreNotEqual(str, cipherStr); Assert.AreNotEqual(str, cipherStr);
@@ -630,6 +629,9 @@ namespace UnitTests.Common.Utilities
CollectionAssert.AreEqual(bytes, plainBytes); CollectionAssert.AreEqual(bytes, plainBytes);
} }
[GeneratedRegex("[^0-9a-f]")]
private static partial Regex RandomStringWithPoolRegex();
#endregion Instance #endregion Instance
} }
} }

View File

@@ -17,11 +17,11 @@ namespace UnitTests.Common.Utilities
// arrange // arrange
int executionCount = 0; int executionCount = 0;
var delay = TimeSpan.FromMilliseconds(100); var delay = TimeSpan.FromMilliseconds(100);
var action = () => { executionCount++; }; void Action() { executionCount++; }
// act // act
var cts = new CancellationTokenSource(delay.Add(TimeSpan.FromSeconds(1))); var cts = new CancellationTokenSource(delay.Add(TimeSpan.FromSeconds(1)));
var delayedTask = DelayedTask.Create(action, delay); var delayedTask = DelayedTask.Create(Action, delay);
SpinWait.SpinUntil(() => executionCount > 0 || cts.IsCancellationRequested); SpinWait.SpinUntil(() => executionCount > 0 || cts.IsCancellationRequested);
// assert // assert
@@ -39,11 +39,11 @@ namespace UnitTests.Common.Utilities
// arrange // arrange
int executionCount = 0; int executionCount = 0;
var delay = TimeSpan.FromMilliseconds(100); var delay = TimeSpan.FromMilliseconds(100);
var action = () => { executionCount++; }; void Action() { executionCount++; }
// act // act
var cts = new CancellationTokenSource(delay.Add(TimeSpan.FromSeconds(1))); var cts = new CancellationTokenSource(delay.Add(TimeSpan.FromSeconds(1)));
var delayedTask = DelayedTask.Run(action, delay); var delayedTask = DelayedTask.Run(Action, delay);
SpinWait.SpinUntil(() => executionCount > 0 || cts.IsCancellationRequested); SpinWait.SpinUntil(() => executionCount > 0 || cts.IsCancellationRequested);
// assert // assert
@@ -61,11 +61,11 @@ namespace UnitTests.Common.Utilities
// arrange // arrange
int executionCount = 0; int executionCount = 0;
var delay = TimeSpan.FromMilliseconds(100); var delay = TimeSpan.FromMilliseconds(100);
var action = () => { executionCount++; }; void Action() { executionCount++; }
// act // act
var cts = new CancellationTokenSource(delay.Add(TimeSpan.FromSeconds(1))); var cts = new CancellationTokenSource(delay.Add(TimeSpan.FromSeconds(1)));
var delayedTask = DelayedTask.Create(action, delay); var delayedTask = DelayedTask.Create(Action, delay);
delayedTask.Reset(); delayedTask.Reset();
SpinWait.SpinUntil(() => executionCount > 0 || cts.IsCancellationRequested); SpinWait.SpinUntil(() => executionCount > 0 || cts.IsCancellationRequested);
@@ -84,11 +84,11 @@ namespace UnitTests.Common.Utilities
// arrange // arrange
int executionCount = 0; int executionCount = 0;
var delay = TimeSpan.FromMilliseconds(100); var delay = TimeSpan.FromMilliseconds(100);
var action = () => { executionCount++; }; void Action() { executionCount++; }
// act // act
var cts = new CancellationTokenSource(delay.Add(TimeSpan.FromSeconds(1))); var cts = new CancellationTokenSource(delay.Add(TimeSpan.FromSeconds(1)));
var delayedTask = DelayedTask.Run(action, delay); var delayedTask = DelayedTask.Run(Action, delay);
delayedTask.Cancel(); delayedTask.Cancel();
SpinWait.SpinUntil(() => executionCount > 0 || cts.IsCancellationRequested); SpinWait.SpinUntil(() => executionCount > 0 || cts.IsCancellationRequested);
@@ -109,12 +109,12 @@ namespace UnitTests.Common.Utilities
var sw = new Stopwatch(); var sw = new Stopwatch();
var delay = TimeSpan.FromMilliseconds(200); var delay = TimeSpan.FromMilliseconds(200);
var action = () => { sw.Stop(); executionCount++; }; void Action() { sw.Stop(); executionCount++; }
// act // act
var cts = new CancellationTokenSource(delay.Add(TimeSpan.FromSeconds(1))); var cts = new CancellationTokenSource(delay.Add(TimeSpan.FromSeconds(1)));
sw.Start(); sw.Start();
var delayedTask = DelayedTask.Run(action, delay); var delayedTask = DelayedTask.Run(Action, delay);
await Task.Delay(50); await Task.Delay(50);
delayedTask.Reset(); delayedTask.Reset();
SpinWait.SpinUntil(() => executionCount > 0 || cts.IsCancellationRequested); SpinWait.SpinUntil(() => executionCount > 0 || cts.IsCancellationRequested);
@@ -138,12 +138,12 @@ namespace UnitTests.Common.Utilities
var sw = new Stopwatch(); var sw = new Stopwatch();
var delay = TimeSpan.FromMilliseconds(200); var delay = TimeSpan.FromMilliseconds(200);
var action = () => { sw.Stop(); executionCount++; }; void Action() { sw.Stop(); executionCount++; }
// act // act
var cts = new CancellationTokenSource(delay.Add(TimeSpan.FromSeconds(1))); var cts = new CancellationTokenSource(delay.Add(TimeSpan.FromSeconds(1)));
sw.Start(); sw.Start();
var delayedTask = DelayedTask.Create(action, delay); var delayedTask = DelayedTask.Create(Action, delay);
await Task.Delay(50); await Task.Delay(50);
bool isSuccess = delayedTask.ExecutePending(); bool isSuccess = delayedTask.ExecutePending();
SpinWait.SpinUntil(() => executionCount > 0 || cts.IsCancellationRequested); SpinWait.SpinUntil(() => executionCount > 0 || cts.IsCancellationRequested);
@@ -169,12 +169,12 @@ namespace UnitTests.Common.Utilities
var sw = new Stopwatch(); var sw = new Stopwatch();
var delay = TimeSpan.FromMilliseconds(200); var delay = TimeSpan.FromMilliseconds(200);
var action = () => { sw.Stop(); executionCount++; }; void Action() { sw.Stop(); executionCount++; }
// act // act
var cts = new CancellationTokenSource(delay.Add(TimeSpan.FromSeconds(1))); var cts = new CancellationTokenSource(delay.Add(TimeSpan.FromSeconds(1)));
sw.Start(); sw.Start();
var delayedTask = DelayedTask.Run(action, delay); var delayedTask = DelayedTask.Run(Action, delay);
await Task.Delay(50); await Task.Delay(50);
bool isSuccess = delayedTask.ExecutePending(); bool isSuccess = delayedTask.ExecutePending();
SpinWait.SpinUntil(() => executionCount > 0 || cts.IsCancellationRequested); SpinWait.SpinUntil(() => executionCount > 0 || cts.IsCancellationRequested);
@@ -197,8 +197,8 @@ namespace UnitTests.Common.Utilities
// arrange // arrange
int executionCount = 0; int executionCount = 0;
var delay = TimeSpan.FromMilliseconds(100); var delay = TimeSpan.FromMilliseconds(100);
var action = () => { executionCount++; }; void Action() { executionCount++; }
var delayedTask = DelayedTask.Create(action, delay); var delayedTask = DelayedTask.Create(Action, delay);
// act // act
delayedTask.Reset(); delayedTask.Reset();
@@ -219,10 +219,10 @@ namespace UnitTests.Common.Utilities
{ {
// arrange // arrange
var delay = TimeSpan.FromMilliseconds(100); var delay = TimeSpan.FromMilliseconds(100);
var action = () => { throw new Exception("TEST :D"); }; static void Action() { throw new Exception("TEST :D"); }
// act // act
var delayedTask = DelayedTask.Run(action, delay); var delayedTask = DelayedTask.Run(Action, delay);
var awaiter = delayedTask.GetAwaiter(); var awaiter = delayedTask.GetAwaiter();
SpinWait.SpinUntil(() => awaiter.IsCompleted); SpinWait.SpinUntil(() => awaiter.IsCompleted);
@@ -240,15 +240,12 @@ namespace UnitTests.Common.Utilities
// arrange // arrange
string exceptionText = null; string exceptionText = null;
var delay = TimeSpan.FromMilliseconds(100); var delay = TimeSpan.FromMilliseconds(100);
var action = () => { throw new Exception("TEST :D"); }; void Action() { throw new Exception("TEST :D"); }
var exceptionHandler = (Exception ex) => void ExceptionHandler(Exception ex) { exceptionText = ex.Message; }
{
exceptionText = ex.Message;
};
// act // act
var delayedTask = DelayedTask.Run(action, delay) var delayedTask = DelayedTask.Run(Action, delay)
.WithExceptionHandler(exceptionHandler); .WithExceptionHandler(ExceptionHandler);
var awaiter = delayedTask.GetAwaiter(); var awaiter = delayedTask.GetAwaiter();
SpinWait.SpinUntil(() => awaiter.IsCompleted); SpinWait.SpinUntil(() => awaiter.IsCompleted);
@@ -267,8 +264,8 @@ namespace UnitTests.Common.Utilities
// arrange // arrange
int executionCount = 0; int executionCount = 0;
var delay = TimeSpan.FromMilliseconds(100); var delay = TimeSpan.FromMilliseconds(100);
var action = () => { executionCount++; }; void Action() { executionCount++; }
var delayedTask = DelayedTask.Create(action, delay); var delayedTask = DelayedTask.Create(Action, delay);
// act // act
delayedTask.Reset(); delayedTask.Reset();

View File

@@ -1,5 +1,4 @@
using System; using System;
using System.Diagnostics;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using AMWD.Common.Utilities; using AMWD.Common.Utilities;
@@ -17,11 +16,11 @@ namespace UnitTests.Common.Utilities
// arrange // arrange
int executionCount = 0; int executionCount = 0;
var delay = TimeSpan.FromMilliseconds(100); var delay = TimeSpan.FromMilliseconds(100);
var function = () => { executionCount++; return new[] { 42, 21 }; }; int[] Function() { executionCount++; return [42, 21]; }
// act // act
var cts = new CancellationTokenSource(delay.Add(TimeSpan.FromSeconds(1))); var cts = new CancellationTokenSource(delay.Add(TimeSpan.FromSeconds(1)));
var delayedTaskWithResult = DelayedTask.Create(function, delay); var delayedTaskWithResult = DelayedTask.Create(Function, delay);
SpinWait.SpinUntil(() => executionCount > 0 || cts.IsCancellationRequested); SpinWait.SpinUntil(() => executionCount > 0 || cts.IsCancellationRequested);
// assert // assert
@@ -34,15 +33,16 @@ namespace UnitTests.Common.Utilities
} }
[TestMethod] [TestMethod]
[System.Diagnostics.CodeAnalysis.SuppressMessage("Performance", "CA1861")]
public async Task ShouldCreateNewDelayedTaskStarting() public async Task ShouldCreateNewDelayedTaskStarting()
{ {
// arrange // arrange
int executionCount = 0; int executionCount = 0;
var delay = TimeSpan.FromMilliseconds(100); var delay = TimeSpan.FromMilliseconds(100);
var function = () => { executionCount++; return new[] { 42, 21 }; }; int[] Function() { executionCount++; return [42, 21]; }
// act // act
var delayedTaskWithResult = DelayedTask.Run(function, delay); var delayedTaskWithResult = DelayedTask.Run(Function, delay);
int[] result = await delayedTaskWithResult; int[] result = await delayedTaskWithResult;
// assert // assert
@@ -61,11 +61,11 @@ namespace UnitTests.Common.Utilities
// arrange // arrange
var delay = TimeSpan.FromMilliseconds(100); var delay = TimeSpan.FromMilliseconds(100);
#pragma warning disable CS0162 // Unreachable Code detected. #pragma warning disable CS0162 // Unreachable Code detected.
var function = () => { throw new Exception("TEST :D"); return new[] { 42, 21 }; }; static int[] Function() { throw new Exception("TEST :D"); return [42, 21]; }
#pragma warning restore CS0162 // Unreachable Code detected #pragma warning restore CS0162 // Unreachable Code detected
// act // act
var delayedTaskWithResult = DelayedTask.Run(function, delay); var delayedTaskWithResult = DelayedTask.Run(Function, delay);
var awaiter = delayedTaskWithResult.GetAwaiter(); var awaiter = delayedTaskWithResult.GetAwaiter();
SpinWait.SpinUntil(() => awaiter.IsCompleted); SpinWait.SpinUntil(() => awaiter.IsCompleted);
@@ -84,16 +84,13 @@ namespace UnitTests.Common.Utilities
string exceptionText = null; string exceptionText = null;
var delay = TimeSpan.FromMilliseconds(100); var delay = TimeSpan.FromMilliseconds(100);
#pragma warning disable CS0162 // Unreachable Code detected. #pragma warning disable CS0162 // Unreachable Code detected.
var function = () => { throw new Exception("TEST :D"); return new[] { 42, 21 }; }; static int[] Function() { throw new Exception("TEST :D"); return [42, 21]; }
#pragma warning restore CS0162 // Unreachable Code detected #pragma warning restore CS0162 // Unreachable Code detected
var exceptionHandler = (Exception ex) => void ExceptionHandler(Exception ex) { exceptionText = ex.Message; }
{
exceptionText = ex.Message;
};
// act // act
var delayedTaskWithResult = DelayedTask.Run(function, delay) var delayedTaskWithResult = DelayedTask.Run(Function, delay)
.WithExceptionHandler(exceptionHandler); .WithExceptionHandler(ExceptionHandler);
var awaiter = delayedTaskWithResult.GetAwaiter(); var awaiter = delayedTaskWithResult.GetAwaiter();
SpinWait.SpinUntil(() => awaiter.IsCompleted); SpinWait.SpinUntil(() => awaiter.IsCompleted);
@@ -107,13 +104,14 @@ namespace UnitTests.Common.Utilities
} }
[TestMethod] [TestMethod]
[System.Diagnostics.CodeAnalysis.SuppressMessage("Performance", "CA1861")]
public async Task ShouldReturnNormalTask() public async Task ShouldReturnNormalTask()
{ {
// arrange // arrange
int executionCount = 0; int executionCount = 0;
var delay = TimeSpan.FromMilliseconds(100); var delay = TimeSpan.FromMilliseconds(100);
var function = () => { executionCount++; return new[] { 42, 21 }; }; int[] Function() { executionCount++; return [42, 21]; }
var delayedTaskWithResult = DelayedTask.Create(function, delay); var delayedTaskWithResult = DelayedTask.Create(Function, delay);
// act // act
delayedTaskWithResult.Reset(); delayedTaskWithResult.Reset();

View File

@@ -7,11 +7,11 @@ namespace UnitTests.Common.Utils
[System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage]
internal class CryptographyHelperSaltMock : IDisposable internal class CryptographyHelperSaltMock : IDisposable
{ {
private readonly int saltLength; private readonly int _saltLength;
private CryptographyHelperSaltMock(int saltLength) private CryptographyHelperSaltMock(int saltLength)
{ {
this.saltLength = typeof(CryptographyHelper).AsDynamicType().saltLength; _saltLength = typeof(CryptographyHelper).AsDynamicType()._saltLength;
SetSaltLength(saltLength); SetSaltLength(saltLength);
} }
@@ -19,9 +19,9 @@ namespace UnitTests.Common.Utils
=> new CryptographyHelperSaltMock(saltLength); => new CryptographyHelperSaltMock(saltLength);
public void Dispose() public void Dispose()
=> SetSaltLength(saltLength); => SetSaltLength(_saltLength);
private static void SetSaltLength(int length) private static void SetSaltLength(int length)
=> typeof(CryptographyHelper).AsDynamicType().saltLength = length; => typeof(CryptographyHelper).AsDynamicType()._saltLength = length;
} }
} }

View File

@@ -4,13 +4,8 @@ namespace UnitTests.Common.Utils
{ {
[System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage]
[AttributeUsage(AttributeTargets.All, AllowMultiple = true)] [AttributeUsage(AttributeTargets.All, AllowMultiple = true)]
internal class CustomMultipleAttribute : Attribute internal class CustomMultipleAttribute(string name) : Attribute
{ {
public CustomMultipleAttribute(string name) public string Name { get; set; } = name;
{
Name = name;
}
public string Name { get; set; }
} }
} }

View File

@@ -33,18 +33,21 @@ namespace UnitTests.Common.Utils
[System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage]
internal class JsonErrorClass internal class JsonErrorClass
{ {
private int? number; private int? _number;
public int Number public int Number
{ {
get get
{ {
if (number.HasValue) if (_number.HasValue)
return number.Value; return _number.Value;
throw new Exception("Null value"); throw new Exception("Null value");
} }
set { number = value; } set
{
_number = value;
}
} }
} }
} }

View File

@@ -6,11 +6,11 @@ namespace UnitTests.Common.Utils
[System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage]
internal class TimeZoneInfoLocalMock : IDisposable internal class TimeZoneInfoLocalMock : IDisposable
{ {
private readonly TimeZoneInfo localTimeZoneInfo; private readonly TimeZoneInfo _localTimeZoneInfo;
private TimeZoneInfoLocalMock(TimeZoneInfo timeZoneInfo) private TimeZoneInfoLocalMock(TimeZoneInfo timeZoneInfo)
{ {
localTimeZoneInfo = TimeZoneInfo.Local; _localTimeZoneInfo = TimeZoneInfo.Local;
SetLocalTimeZone(timeZoneInfo); SetLocalTimeZone(timeZoneInfo);
} }
@@ -18,7 +18,7 @@ namespace UnitTests.Common.Utils
=> new TimeZoneInfoLocalMock(mockTimeZoneInfo); => new TimeZoneInfoLocalMock(mockTimeZoneInfo);
public void Dispose() public void Dispose()
=> SetLocalTimeZone(localTimeZoneInfo); => SetLocalTimeZone(_localTimeZoneInfo);
private static void SetLocalTimeZone(TimeZoneInfo timeZoneInfo) private static void SetLocalTimeZone(TimeZoneInfo timeZoneInfo)
=> typeof(TimeZoneInfo).AsDynamicType().s_cachedData._localTimeZone = timeZoneInfo; => typeof(TimeZoneInfo).AsDynamicType().s_cachedData._localTimeZone = timeZoneInfo;

View File

@@ -1,24 +1,23 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<TargetFramework>net6.0</TargetFramework> <TargetFramework>net8.0</TargetFramework>
<IsPackable>false</IsPackable> <IsPackable>false</IsPackable>
<CollectCoverage>true</CollectCoverage> <CollectCoverage>true</CollectCoverage>
<GenerateDocumentationFile>false</GenerateDocumentationFile> <GenerateDocumentationFile>false</GenerateDocumentationFile>
<Configurations>Debug;Release;DebugLocal</Configurations>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="coverlet.msbuild" Version="3.2.0"> <PackageReference Include="coverlet.msbuild" Version="6.0.0">
<PrivateAssets>all</PrivateAssets> <PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference> </PackageReference>
<PackageReference Include="DNS" Version="7.0.0" /> <PackageReference Include="DNS" Version="7.0.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.5.0" /> <PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.7.2" />
<PackageReference Include="Moq" Version="4.18.4" /> <PackageReference Include="Moq" Version="4.20.69" />
<PackageReference Include="MSTest.TestAdapter" Version="3.0.2" /> <PackageReference Include="MSTest.TestAdapter" Version="3.1.1" />
<PackageReference Include="MSTest.TestFramework" Version="3.0.2" /> <PackageReference Include="MSTest.TestFramework" Version="3.1.1" />
<PackageReference Include="ReflectionMagic" Version="4.1.0" /> <PackageReference Include="ReflectionMagic" Version="5.0.0" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>

8
nuget.config Normal file
View File

@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<packageSources>
<clear />
<add key="nuget.org" value="https://api.nuget.org/v3/index.json" protocolVersion="3" />
<add key="AM.WD" value="https://nuget.am-wd.de/v3/index.json" />
</packageSources>
</configuration>