Merge branch 'main' into packing
This commit is contained in:
@@ -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.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]
|
||||
csharp_preferred_modifier_order = public, private, protected, internal, static, extern, new, virtual, abstract, sealed, override, readonly, unsafe, volatile, async:suggestion
|
||||
|
||||
|
||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -11,7 +11,6 @@
|
||||
*.userosscache
|
||||
*.sln.docstates
|
||||
|
||||
nuget.config
|
||||
build
|
||||
coverage.json
|
||||
|
||||
|
||||
252
.gitlab-ci.yml
252
.gitlab-ci.yml
@@ -1,115 +1,151 @@
|
||||
# The image has to use the same version as the .NET UnitTest project
|
||||
image: mcr.microsoft.com/dotnet/sdk:6.0
|
||||
|
||||
variables:
|
||||
TZ: "Europe/Berlin"
|
||||
LANG: "de"
|
||||
|
||||
|
||||
|
||||
stages:
|
||||
- build
|
||||
- test
|
||||
- deploy
|
||||
|
||||
|
||||
|
||||
debug-build:
|
||||
stage: build
|
||||
tags:
|
||||
- docker
|
||||
- lnx
|
||||
except:
|
||||
- tags
|
||||
script:
|
||||
- dotnet restore --no-cache --force
|
||||
- dotnet build -c Debug --nologo --no-restore --no-incremental
|
||||
- mkdir ./artifacts
|
||||
- mv ./AMWD.Common/bin/Debug/*.nupkg ./artifacts/
|
||||
- mv ./AMWD.Common/bin/Debug/*.snupkg ./artifacts/
|
||||
- mv ./AMWD.Common.AspNetCore/bin/Debug/*.nupkg ./artifacts/
|
||||
- mv ./AMWD.Common.AspNetCore/bin/Debug/*.snupkg ./artifacts/
|
||||
- mv ./AMWD.Common.EntityFrameworkCore/bin/Debug/*.nupkg ./artifacts/
|
||||
- mv ./AMWD.Common.EntityFrameworkCore/bin/Debug/*.snupkg ./artifacts/
|
||||
- mv ./AMWD.Common.Moq/bin/Debug/*.nupkg ./artifacts/
|
||||
- mv ./AMWD.Common.Moq/bin/Debug/*.snupkg ./artifacts/
|
||||
artifacts:
|
||||
paths:
|
||||
- artifacts/*.nupkg
|
||||
- artifacts/*.snupkg
|
||||
expire_in: 7 days
|
||||
|
||||
debug-test:
|
||||
# The image should use the same version as the UnitTests are
|
||||
image: mcr.microsoft.com/dotnet/sdk:8.0
|
||||
|
||||
variables:
|
||||
TZ: Europe/Berlin
|
||||
LANG: de
|
||||
|
||||
stages:
|
||||
- build
|
||||
- test
|
||||
- deploy
|
||||
|
||||
|
||||
|
||||
build-debug:
|
||||
stage: build
|
||||
tags:
|
||||
- docker
|
||||
- lnx
|
||||
rules:
|
||||
- if: $CI_COMMIT_TAG == null
|
||||
script:
|
||||
- dotnet restore --no-cache --force
|
||||
- dotnet build -c Debug --nologo --no-restore --no-incremental
|
||||
- mkdir ./artifacts
|
||||
- mv ./AMWD.Common/bin/Debug/*.nupkg ./artifacts/
|
||||
- mv ./AMWD.Common/bin/Debug/*.snupkg ./artifacts/
|
||||
- mv ./AMWD.Common.AspNetCore/bin/Debug/*.nupkg ./artifacts/
|
||||
- mv ./AMWD.Common.AspNetCore/bin/Debug/*.snupkg ./artifacts/
|
||||
- mv ./AMWD.Common.EntityFrameworkCore/bin/Debug/*.nupkg ./artifacts/
|
||||
- mv ./AMWD.Common.EntityFrameworkCore/bin/Debug/*.snupkg ./artifacts/
|
||||
- mv ./AMWD.Common.Test/bin/Debug/*.nupkg ./artifacts/
|
||||
- mv ./AMWD.Common.Test/bin/Debug/*.snupkg ./artifacts/
|
||||
artifacts:
|
||||
paths:
|
||||
- artifacts/*.nupkg
|
||||
- artifacts/*.snupkg
|
||||
expire_in: 7 days
|
||||
|
||||
test-debug:
|
||||
stage: test
|
||||
dependencies:
|
||||
- debug-build
|
||||
tags:
|
||||
- docker
|
||||
- lnx
|
||||
except:
|
||||
- tags
|
||||
# branch-coverage
|
||||
coverage: '/Total[^|]*\|[^|]*\|\s*([0-9.%]+)/'
|
||||
script:
|
||||
- dotnet restore --no-cache --force
|
||||
dependencies:
|
||||
- build-debug
|
||||
tags:
|
||||
- docker
|
||||
- lnx
|
||||
rules:
|
||||
- if: $CI_COMMIT_TAG == null
|
||||
# line-coverage
|
||||
#coverage: '/Total[^|]*\|\s*([0-9.%]+)/'
|
||||
# branch-coverage
|
||||
coverage: '/Total[^|]*\|[^|]*\|\s*([0-9.%]+)/'
|
||||
script:
|
||||
- dotnet restore --no-cache --force
|
||||
- dotnet test -c Debug --nologo --no-restore
|
||||
|
||||
|
||||
|
||||
|
||||
release-build:
|
||||
stage: build
|
||||
tags:
|
||||
- docker
|
||||
- lnx
|
||||
- amd64
|
||||
only:
|
||||
- tags
|
||||
script:
|
||||
- dotnet restore --no-cache --force
|
||||
- dotnet build -c Release --nologo --no-restore --no-incremental
|
||||
- mkdir ./artifacts
|
||||
- mv ./AMWD.Common/bin/Release/*.nupkg ./artifacts/
|
||||
- mv ./AMWD.Common/bin/Release/*.snupkg ./artifacts/
|
||||
- mv ./AMWD.Common.AspNetCore/bin/Release/*.nupkg ./artifacts/
|
||||
- mv ./AMWD.Common.AspNetCore/bin/Release/*.snupkg ./artifacts/
|
||||
- mv ./AMWD.Common.EntityFrameworkCore/bin/Release/*.nupkg ./artifacts/
|
||||
- mv ./AMWD.Common.EntityFrameworkCore/bin/Release/*.snupkg ./artifacts/
|
||||
- mv ./AMWD.Common.Moq/bin/Release/*.nupkg ./artifacts/
|
||||
- mv ./AMWD.Common.Moq/bin/Release/*.snupkg ./artifacts/
|
||||
artifacts:
|
||||
paths:
|
||||
- artifacts/*.nupkg
|
||||
- artifacts/*.snupkg
|
||||
expire_in: 1 day
|
||||
|
||||
release-test:
|
||||
|
||||
build-release:
|
||||
stage: build
|
||||
tags:
|
||||
- docker
|
||||
- lnx
|
||||
rules:
|
||||
- if: $CI_COMMIT_TAG != null
|
||||
script:
|
||||
- dotnet restore --no-cache --force
|
||||
- dotnet build -c Release --nologo --no-restore --no-incremental
|
||||
- mkdir ./artifacts
|
||||
- mv ./AMWD.Common/bin/Release/*.nupkg ./artifacts/
|
||||
- mv ./AMWD.Common/bin/Release/*.snupkg ./artifacts/
|
||||
- mv ./AMWD.Common.AspNetCore/bin/Release/*.nupkg ./artifacts/
|
||||
- mv ./AMWD.Common.AspNetCore/bin/Release/*.snupkg ./artifacts/
|
||||
- mv ./AMWD.Common.EntityFrameworkCore/bin/Release/*.nupkg ./artifacts/
|
||||
- mv ./AMWD.Common.EntityFrameworkCore/bin/Release/*.snupkg ./artifacts/
|
||||
- mv ./AMWD.Common.Test/bin/Release/*.nupkg ./artifacts/
|
||||
- mv ./AMWD.Common.Test/bin/Release/*.snupkg ./artifacts/
|
||||
artifacts:
|
||||
paths:
|
||||
- artifacts/*.nupkg
|
||||
- artifacts/*.snupkg
|
||||
expire_in: 1 days
|
||||
|
||||
test-release:
|
||||
stage: test
|
||||
dependencies:
|
||||
- release-build
|
||||
tags:
|
||||
- docker
|
||||
- lnx
|
||||
- amd64
|
||||
only:
|
||||
- tags
|
||||
# line-coverage
|
||||
coverage: '/Total[^|]*\|\s*([0-9.%]+)/'
|
||||
script:
|
||||
- dotnet restore --no-cache --force
|
||||
- dotnet test -c Release --nologo --no-restore
|
||||
|
||||
|
||||
release-deploy:
|
||||
dependencies:
|
||||
- build-release
|
||||
tags:
|
||||
- docker
|
||||
- lnx
|
||||
rules:
|
||||
- if: $CI_COMMIT_TAG != null
|
||||
# line-coverage
|
||||
#coverage: '/Total[^|]*\|\s*([0-9.%]+)/'
|
||||
# branch-coverage
|
||||
coverage: '/Total[^|]*\|[^|]*\|\s*([0-9.%]+)/'
|
||||
script:
|
||||
- dotnet restore --no-cache --force
|
||||
- dotnet test -c Release --nologo --no-restore
|
||||
|
||||
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
|
||||
dependencies:
|
||||
- release-build
|
||||
- release-test
|
||||
tags:
|
||||
- docker
|
||||
- lnx
|
||||
- amd64
|
||||
only:
|
||||
- tags
|
||||
script:
|
||||
- dotnet nuget push -k $BAGET_APIKEY -s https://nuget.am-wd.de/v3/index.json --skip-duplicate artifacts/*.nupkg
|
||||
- build-release
|
||||
- test-release
|
||||
tags:
|
||||
- docker
|
||||
- lnx
|
||||
rules:
|
||||
- if: $CI_COMMIT_TAG =~ /^asp\/v[0-9.]+/
|
||||
script:
|
||||
- dotnet nuget push -k $BAGET_APIKEY -s https://nuget.am-wd.de/v3/index.json --skip-duplicate artifacts/AMWD.Common.AspNetCore.*.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
|
||||
|
||||
@@ -1,41 +1,30 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<Configurations>Debug;Release;DebugLocal</Configurations>
|
||||
<TargetFrameworks>netcoreapp3.1;net6.0</TargetFrameworks>
|
||||
<TargetFrameworks>net6.0;net8.0</TargetFrameworks>
|
||||
<LangVersion>10.0</LangVersion>
|
||||
|
||||
<NrtTagMatch>asp/v[0-9]*</NrtTagMatch>
|
||||
<AssemblyName>AMWD.Common.AspNetCore</AssemblyName>
|
||||
<RootNamespace>AMWD.Common.AspNetCore</RootNamespace>
|
||||
|
||||
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
|
||||
<PackageId>AMWD.Common.AspNetCore</PackageId>
|
||||
<PackageIcon>icon.png</PackageIcon>
|
||||
<PackageReadmeFile>README.md</PackageReadmeFile>
|
||||
|
||||
<Product>AM.WD Common Library for ASP.NET Core</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="/" />
|
||||
<None Include="../README.md" Pack="true" PackagePath="/" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<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.NetRevisionTask" Version="0.4.3">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
@@ -4,7 +4,7 @@ using System.Net.Http.Headers;
|
||||
using System.Security.Claims;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using AMWD.Common.AspNetCore.BasicAuthentication;
|
||||
using AMWD.Common.AspNetCore.Security.BasicAuthentication;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.Mvc.Filters;
|
||||
@@ -40,18 +40,18 @@ namespace Microsoft.AspNetCore.Authorization
|
||||
var logger = context.HttpContext.RequestServices.GetService<ILogger<BasicAuthenticationAttribute>>();
|
||||
try
|
||||
{
|
||||
var validatorResult = await TrySetHttpUser(context);
|
||||
var validatorResult = await TrySetHttpUser(context).ConfigureAwait(false);
|
||||
bool isAllowAnonymous = context.ActionDescriptor.EndpointMetadata.OfType<AllowAnonymousAttribute>().Any();
|
||||
if (isAllowAnonymous)
|
||||
return;
|
||||
|
||||
if (!context.HttpContext.Request.Headers.ContainsKey("Authorization"))
|
||||
if (!context.HttpContext.Request.Headers.TryGetValue("Authorization", out var authHeaderValue))
|
||||
{
|
||||
SetAuthenticateRequest(context);
|
||||
return;
|
||||
}
|
||||
|
||||
var authHeader = AuthenticationHeaderValue.Parse(context.HttpContext.Request.Headers["Authorization"]);
|
||||
var authHeader = AuthenticationHeaderValue.Parse(authHeaderValue);
|
||||
byte[] decoded = Convert.FromBase64String(authHeader.Parameter);
|
||||
string plain = Encoding.UTF8.GetString(decoded);
|
||||
|
||||
@@ -84,22 +84,22 @@ namespace Microsoft.AspNetCore.Authorization
|
||||
: validator.Realm
|
||||
: Realm;
|
||||
|
||||
context.HttpContext.Response.Headers["WWW-Authenticate"] = "Basic";
|
||||
context.HttpContext.Response.Headers.WWWAuthenticate = "Basic";
|
||||
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.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>>();
|
||||
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);
|
||||
string plain = Encoding.UTF8.GetString(decoded);
|
||||
|
||||
@@ -111,7 +111,7 @@ namespace Microsoft.AspNetCore.Authorization
|
||||
if (validator == 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)
|
||||
return null;
|
||||
|
||||
|
||||
@@ -50,7 +50,7 @@ namespace Microsoft.AspNetCore.Mvc.Filters
|
||||
|
||||
private const string VerificationUrl = "https://www.google.com/recaptcha/api/siteverify";
|
||||
|
||||
private string privateKey;
|
||||
private string _privateKey;
|
||||
|
||||
/// <summary>
|
||||
/// Executes the validattion in background.
|
||||
@@ -61,13 +61,13 @@ namespace Microsoft.AspNetCore.Mvc.Filters
|
||||
public override async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next)
|
||||
{
|
||||
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;
|
||||
|
||||
await DoValidation(context);
|
||||
await base.OnActionExecutionAsync(context, next);
|
||||
await DoValidation(context).ConfigureAwait(false);
|
||||
await base.OnActionExecutionAsync(context, next).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
private async Task DoValidation(ActionExecutingContext context)
|
||||
@@ -82,7 +82,7 @@ namespace Microsoft.AspNetCore.Mvc.Filters
|
||||
return;
|
||||
}
|
||||
|
||||
await Validate(context, token);
|
||||
await Validate(context, token).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
private async Task Validate(ActionExecutingContext context, string token)
|
||||
@@ -90,11 +90,11 @@ namespace Microsoft.AspNetCore.Mvc.Filters
|
||||
using var httpClient = new HttpClient();
|
||||
var param = new Dictionary<string, string>
|
||||
{
|
||||
{ "secret", privateKey },
|
||||
{ "secret", _privateKey },
|
||||
{ "response", token }
|
||||
};
|
||||
var response = await httpClient.PostAsync(VerificationUrl, new FormUrlEncodedContent(param));
|
||||
string json = await response.Content.ReadAsStringAsync();
|
||||
var response = await httpClient.PostAsync(VerificationUrl, new FormUrlEncodedContent(param)).ConfigureAwait(false);
|
||||
string json = await response.Content.ReadAsStringAsync().ConfigureAwait(false);
|
||||
|
||||
var result = JsonConvert.DeserializeObject<Response>(json);
|
||||
if (result?.Success != true)
|
||||
|
||||
@@ -1,16 +1,16 @@
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.HttpOverrides;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using IPNetwork = Microsoft.AspNetCore.HttpOverrides.IPNetwork;
|
||||
|
||||
namespace Microsoft.AspNetCore.Mvc.Filters
|
||||
{
|
||||
/// <summary>
|
||||
/// Implements an IP filter. Only defined addresses are allowed to access.
|
||||
/// </summary>
|
||||
public class IPWhitelistAttribute : ActionFilterAttribute
|
||||
public class IPAllowListAttribute : ActionFilterAttribute
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether local (localhost) access is granted (Default: true).
|
||||
@@ -1,21 +1,21 @@
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.HttpOverrides;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using IPNetwork = Microsoft.AspNetCore.HttpOverrides.IPNetwork;
|
||||
|
||||
namespace Microsoft.AspNetCore.Mvc.Filters
|
||||
{
|
||||
/// <summary>
|
||||
/// Implements an IP filter. The defined addresses are blocked.
|
||||
/// </summary>
|
||||
public class IPBlacklistAttribute : ActionFilterAttribute
|
||||
public class IPBlockListAttribute : ActionFilterAttribute
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether local (localhost) access is blocked (Default: false).
|
||||
/// </summary>
|
||||
public bool RestrictLocalAccess { get; set; }
|
||||
public bool BlockLocalAccess { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a configuration key where the blocked IP addresses are defined.
|
||||
@@ -35,7 +35,7 @@ namespace Microsoft.AspNetCore.Mvc.Filters
|
||||
/// <summary>
|
||||
/// Gets or sets a comma separated list of blocked IP addresses.
|
||||
/// </summary>
|
||||
public string RestrictedIpAddresses { get; set; }
|
||||
public string BlockedIpAddresses { get; set; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void OnActionExecuting(ActionExecutingContext context)
|
||||
@@ -43,13 +43,13 @@ namespace Microsoft.AspNetCore.Mvc.Filters
|
||||
base.OnActionExecuting(context);
|
||||
context.HttpContext.Items["RemoteAddress"] = context.HttpContext.GetRemoteIpAddress();
|
||||
|
||||
if (!RestrictLocalAccess && context.HttpContext.IsLocalRequest())
|
||||
if (!BlockLocalAccess && context.HttpContext.IsLocalRequest())
|
||||
return;
|
||||
|
||||
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)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(ipAddress))
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2,6 +2,7 @@
|
||||
using System.Net;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.HttpOverrides;
|
||||
using IPNetwork = Microsoft.AspNetCore.HttpOverrides.IPNetwork;
|
||||
|
||||
namespace Microsoft.AspNetCore.Builder
|
||||
{
|
||||
@@ -20,15 +21,19 @@ namespace Microsoft.AspNetCore.Builder
|
||||
/// <br/>
|
||||
/// Additionally you can specify the proxy server by using <paramref name="address"/> or a <paramref name="network"/> when there are multiple proxy servers.
|
||||
/// <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>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>
|
||||
/// <param name="app">The application builder.</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>
|
||||
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");
|
||||
if (!string.IsNullOrWhiteSpace(path))
|
||||
@@ -40,9 +45,17 @@ namespace Microsoft.AspNetCore.Builder
|
||||
|
||||
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("172.16.0.0"), 12));
|
||||
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)
|
||||
@@ -52,6 +65,8 @@ namespace Microsoft.AspNetCore.Builder
|
||||
options.KnownProxies.Add(address);
|
||||
|
||||
app.UseForwardedHeaders(options);
|
||||
|
||||
return app;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
using System.Net;
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using Microsoft.AspNetCore.Antiforgery;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
|
||||
@@ -9,43 +11,90 @@ namespace Microsoft.AspNetCore.Http
|
||||
/// </summary>
|
||||
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>
|
||||
/// Retrieves the antiforgery token.
|
||||
/// </summary>
|
||||
/// <param name="httpContext">The current <see cref="HttpContext"/>.</param>
|
||||
/// <returns>Name and value of the token.</returns>
|
||||
public static (string Name, string Value) GetAntiforgeryToken(this HttpContext httpContext)
|
||||
/// <returns>FormName, HeaderName and Value of the antiforgery token.</returns>
|
||||
public static (string FormName, string HeaderName, string Value) GetAntiforgeryToken(this HttpContext httpContext)
|
||||
{
|
||||
var af = httpContext.RequestServices.GetService<IAntiforgery>();
|
||||
var set = af?.GetAndStoreTokens(httpContext);
|
||||
var antiforgery = httpContext.RequestServices.GetService<IAntiforgery>();
|
||||
var tokenSet = antiforgery?.GetAndStoreTokens(httpContext);
|
||||
|
||||
return (Name: set?.FormFieldName, Value: set?.RequestToken);
|
||||
return (tokenSet?.FormFieldName, tokenSet?.HeaderName, tokenSet?.RequestToken);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the remote ip address.
|
||||
/// </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="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>
|
||||
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();
|
||||
if (!string.IsNullOrWhiteSpace(forwardedHeader) && IPAddress.TryParse(forwardedHeader, out var forwarded))
|
||||
return forwarded;
|
||||
string forwardedForAddress = null;
|
||||
|
||||
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>
|
||||
/// Returns whether the request was made locally.
|
||||
/// </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="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>
|
||||
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);
|
||||
}
|
||||
|
||||
|
||||
@@ -15,11 +15,11 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
||||
/// <typeparam name="TModel">The type of the model.</typeparam>
|
||||
/// <typeparam name="TProperty">The type of the property.</typeparam>
|
||||
/// <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="errorMessage">The error message to add.</param>
|
||||
/// <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)
|
||||
throw new ArgumentNullException(nameof(modelState));
|
||||
|
||||
@@ -18,7 +18,8 @@ namespace Microsoft.Extensions.DependencyInjection
|
||||
where TService : class, IHostedService
|
||||
{
|
||||
services.AddSingleton<TService>();
|
||||
services.AddSingleton<IHostedService, BackgroundServiceStarter<TService>>();
|
||||
services.AddHostedService(serviceProvider => serviceProvider.GetRequiredService<TService>());
|
||||
|
||||
return services;
|
||||
}
|
||||
|
||||
@@ -30,10 +31,11 @@ namespace Microsoft.Extensions.DependencyInjection
|
||||
/// <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>
|
||||
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<IHostedService, BackgroundServiceStarter<TService>>();
|
||||
services.AddHostedService(serviceProvider => serviceProvider.GetRequiredService<TService>());
|
||||
|
||||
return services;
|
||||
}
|
||||
|
||||
@@ -12,9 +12,9 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
||||
[System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage]
|
||||
public class InvariantFloatingPointModelBinder : IModelBinder
|
||||
{
|
||||
private readonly NumberStyles supportedNumberStyles;
|
||||
private readonly ILogger logger;
|
||||
private readonly CultureInfo cultureInfo;
|
||||
private readonly NumberStyles _supportedNumberStyles;
|
||||
private readonly ILogger _logger;
|
||||
private readonly CultureInfo _cultureInfo;
|
||||
|
||||
/// <summary>
|
||||
/// 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>
|
||||
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;
|
||||
logger = loggerFactory?.CreateLogger<InvariantFloatingPointModelBinder>();
|
||||
_supportedNumberStyles = supportedStyles;
|
||||
_logger = loggerFactory?.CreateLogger<InvariantFloatingPointModelBinder>();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
@@ -36,15 +36,15 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
||||
if (bindingContext == null)
|
||||
throw new ArgumentNullException(nameof(bindingContext));
|
||||
|
||||
logger?.AttemptingToBindModel(bindingContext);
|
||||
_logger?.AttemptingToBindModel(bindingContext);
|
||||
string modelName = bindingContext.ModelName;
|
||||
var valueProviderResult = bindingContext.ValueProvider.GetValue(modelName);
|
||||
if (valueProviderResult == ValueProviderResult.None)
|
||||
{
|
||||
logger?.FoundNoValueInRequest(bindingContext);
|
||||
_logger?.FoundNoValueInRequest(bindingContext);
|
||||
|
||||
// no entry
|
||||
logger?.DoneAttemptingToBindModel(bindingContext);
|
||||
_logger?.DoneAttemptingToBindModel(bindingContext);
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
@@ -56,7 +56,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
||||
try
|
||||
{
|
||||
string value = valueProviderResult.FirstValue;
|
||||
var culture = cultureInfo ?? valueProviderResult.Culture;
|
||||
var culture = _cultureInfo ?? valueProviderResult.Culture;
|
||||
|
||||
object model;
|
||||
if (string.IsNullOrWhiteSpace(value))
|
||||
@@ -66,15 +66,15 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
||||
}
|
||||
else if (type == typeof(float))
|
||||
{
|
||||
model = float.Parse(value, supportedNumberStyles, culture);
|
||||
model = float.Parse(value, _supportedNumberStyles, culture);
|
||||
}
|
||||
else if (type == typeof(double))
|
||||
{
|
||||
model = double.Parse(value, supportedNumberStyles, culture);
|
||||
model = double.Parse(value, _supportedNumberStyles, culture);
|
||||
}
|
||||
else if (type == typeof(decimal))
|
||||
{
|
||||
model = decimal.Parse(value, supportedNumberStyles, culture);
|
||||
model = decimal.Parse(value, _supportedNumberStyles, culture);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -113,7 +113,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
||||
// Conversion failed.
|
||||
}
|
||||
|
||||
logger?.DoneAttemptingToBindModel(bindingContext);
|
||||
_logger?.DoneAttemptingToBindModel(bindingContext);
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -6,15 +6,15 @@ using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace AMWD.Common.AspNetCore.BasicAuthentication
|
||||
namespace AMWD.Common.AspNetCore.Security.BasicAuthentication
|
||||
{
|
||||
/// <summary>
|
||||
/// Implements a basic authentication.
|
||||
/// </summary>
|
||||
public class BasicAuthenticationMiddleware
|
||||
{
|
||||
private readonly RequestDelegate next;
|
||||
private readonly IBasicAuthenticationValidator validator;
|
||||
private readonly RequestDelegate _next;
|
||||
private readonly IBasicAuthenticationValidator _validator;
|
||||
|
||||
/// <summary>
|
||||
/// 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>
|
||||
public BasicAuthenticationMiddleware(RequestDelegate next, IBasicAuthenticationValidator validator)
|
||||
{
|
||||
this.next = next;
|
||||
this.validator = validator;
|
||||
_next = next;
|
||||
_validator = validator;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -37,7 +37,7 @@ namespace AMWD.Common.AspNetCore.BasicAuthentication
|
||||
{
|
||||
if (!httpContext.Request.Headers.ContainsKey("Authorization"))
|
||||
{
|
||||
SetAuthenticateRequest(httpContext, validator.Realm);
|
||||
SetAuthenticateRequest(httpContext, _validator.Realm);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -51,14 +51,14 @@ namespace AMWD.Common.AspNetCore.BasicAuthentication
|
||||
string username = plain.Split(':').First();
|
||||
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)
|
||||
{
|
||||
SetAuthenticateRequest(httpContext, validator.Realm);
|
||||
SetAuthenticateRequest(httpContext, _validator.Realm);
|
||||
return;
|
||||
}
|
||||
|
||||
await next.Invoke(httpContext);
|
||||
await _next.Invoke(httpContext).ConfigureAwait(false);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
@@ -3,7 +3,7 @@ using System.Security.Claims;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace AMWD.Common.AspNetCore.BasicAuthentication
|
||||
namespace AMWD.Common.AspNetCore.Security.BasicAuthentication
|
||||
{
|
||||
/// <summary>
|
||||
/// Interface representing the validation of a basic authentication.
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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; }
|
||||
}
|
||||
}
|
||||
@@ -21,7 +21,7 @@ namespace Microsoft.AspNetCore.Razor.TagHelpers
|
||||
[HtmlAttributeName("class")]
|
||||
public string CssClass { get; set; }
|
||||
|
||||
private IDictionary<string, bool> classValues;
|
||||
private IDictionary<string, bool> _classValues;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a dictionary containing all conditional class names and a boolean condition
|
||||
@@ -32,11 +32,11 @@ namespace Microsoft.AspNetCore.Razor.TagHelpers
|
||||
{
|
||||
get
|
||||
{
|
||||
return classValues ??= new Dictionary<string, bool>(StringComparer.OrdinalIgnoreCase);
|
||||
return _classValues ??= new Dictionary<string, bool>(StringComparer.OrdinalIgnoreCase);
|
||||
}
|
||||
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>
|
||||
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))
|
||||
items.Insert(0, CssClass);
|
||||
|
||||
|
||||
@@ -18,8 +18,8 @@ namespace Microsoft.AspNetCore.Razor.TagHelpers
|
||||
[HtmlTargetElement("script")]
|
||||
public class IntegrityHashTagHelper : TagHelper
|
||||
{
|
||||
private readonly IWebHostEnvironment env;
|
||||
private readonly string hostUrl;
|
||||
private readonly IWebHostEnvironment _env;
|
||||
private readonly string _hostUrl;
|
||||
|
||||
/// <summary>
|
||||
/// 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>
|
||||
public IntegrityHashTagHelper(IWebHostEnvironment env, IConfiguration configuration)
|
||||
{
|
||||
this.env = env;
|
||||
hostUrl = configuration.GetValue("ASPNETCORE_APPL_URL", "http://localhost/");
|
||||
_env = env;
|
||||
_hostUrl = configuration.GetValue("ASPNETCORE_APPL_URL", "http://localhost/");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -84,11 +84,11 @@ namespace Microsoft.AspNetCore.Razor.TagHelpers
|
||||
{
|
||||
using var client = new HttpClient();
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(hostUrl))
|
||||
client.DefaultRequestHeaders.Referrer = new Uri(hostUrl);
|
||||
if (!string.IsNullOrWhiteSpace(_hostUrl))
|
||||
client.DefaultRequestHeaders.Referrer = new Uri(_hostUrl);
|
||||
|
||||
var response = await client.GetAsync(source);
|
||||
fileBytes = await response.Content.ReadAsByteArrayAsync();
|
||||
var response = await client.GetAsync(source).ConfigureAwait(false);
|
||||
fileBytes = await response.Content.ReadAsByteArrayAsync().ConfigureAwait(false);
|
||||
}
|
||||
catch
|
||||
{
|
||||
@@ -103,13 +103,13 @@ namespace Microsoft.AspNetCore.Razor.TagHelpers
|
||||
if (source.StartsWith("/"))
|
||||
source = source[1..];
|
||||
|
||||
if (source.Contains("?"))
|
||||
if (source.Contains('?'))
|
||||
source = source[..source.IndexOf("?")];
|
||||
|
||||
try
|
||||
{
|
||||
string path = Path.Combine(env.WebRootPath, source);
|
||||
fileBytes = await File.ReadAllBytesAsync(path);
|
||||
string path = Path.Combine(_env.WebRootPath, source);
|
||||
fileBytes = await File.ReadAllBytesAsync(path).ConfigureAwait(false);
|
||||
}
|
||||
catch
|
||||
{
|
||||
@@ -118,7 +118,7 @@ namespace Microsoft.AspNetCore.Razor.TagHelpers
|
||||
}
|
||||
|
||||
string type;
|
||||
byte[] hashBytes = new byte[0];
|
||||
byte[] hashBytes = Array.Empty<byte>();
|
||||
switch (IntegrityStrength)
|
||||
{
|
||||
case 512:
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,51 +1,38 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<Configurations>Debug;Release;DebugLocal</Configurations>
|
||||
<TargetFrameworks>netcoreapp3.1;net6.0</TargetFrameworks>
|
||||
<TargetFrameworks>net6.0;net8.0</TargetFrameworks>
|
||||
<LangVersion>10.0</LangVersion>
|
||||
|
||||
<NrtTagMatch>efc/v[0-9]*</NrtTagMatch>
|
||||
<AssemblyName>AMWD.Common.EntityFrameworkCore</AssemblyName>
|
||||
<RootNamespace>AMWD.Common.EntityFrameworkCore</RootNamespace>
|
||||
|
||||
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
|
||||
<PackageId>AMWD.Common.EntityFrameworkCore</PackageId>
|
||||
<PackageIcon>icon.png</PackageIcon>
|
||||
<PackageReadmeFile>README.md</PackageReadmeFile>
|
||||
|
||||
<Product>AM.WD Common Library for EntityFramework Core</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 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" />
|
||||
<None Include="../README.md" Pack="true" PackagePath="/" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup Condition="'$(TargetFramework)' == 'net6.0'">
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="6.0.9" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Relational" Version="6.0.9" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="6.0.25" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Relational" Version="6.0.25" />
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration" Version="6.0.1" />
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration.Binder" Version="6.0.0" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Unclassified.NetRevisionTask" Version="0.4.3">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
<ItemGroup Condition="'$(TargetFramework)' == 'net8.0'">
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="8.0.0" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Relational" Version="8.0.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration" Version="8.0.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration.Binder" Version="8.0.0" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
#if NET6_0_OR_GREATER
|
||||
|
||||
using System;
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||
|
||||
namespace AMWD.Common.EntityFrameworkCore.Converters
|
||||
@@ -24,5 +22,3 @@ namespace AMWD.Common.EntityFrameworkCore.Converters
|
||||
{ }
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
#if NET6_0_OR_GREATER
|
||||
|
||||
using System;
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||
|
||||
namespace AMWD.Common.EntityFrameworkCore.Converters
|
||||
@@ -24,5 +22,3 @@ namespace AMWD.Common.EntityFrameworkCore.Converters
|
||||
{ }
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
#if NET6_0_OR_GREATER
|
||||
|
||||
using System;
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||
|
||||
namespace AMWD.Common.EntityFrameworkCore.Converters
|
||||
@@ -24,5 +22,3 @@ namespace AMWD.Common.EntityFrameworkCore.Converters
|
||||
{ }
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
#if NET6_0_OR_GREATER
|
||||
|
||||
using System;
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||
|
||||
namespace AMWD.Common.EntityFrameworkCore.Converters
|
||||
@@ -24,5 +22,3 @@ namespace AMWD.Common.EntityFrameworkCore.Converters
|
||||
{ }
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
@@ -33,6 +33,8 @@ namespace System
|
||||
: base(message, innerException)
|
||||
{ }
|
||||
|
||||
#if NET6_0
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="DatabaseProviderException"/> class with serialized data.
|
||||
/// </summary>
|
||||
@@ -43,5 +45,7 @@ namespace System
|
||||
protected DatabaseProviderException(SerializationInfo info, StreamingContext context)
|
||||
: base(info, context)
|
||||
{ }
|
||||
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,6 +24,7 @@ namespace Microsoft.EntityFrameworkCore
|
||||
/// <param name="optionsAction">An action to set additional options.</param>
|
||||
/// <param name="cancellationToken">The cancellation token.</param>
|
||||
/// <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)
|
||||
{
|
||||
if (database == null)
|
||||
@@ -45,21 +46,21 @@ namespace Microsoft.EntityFrameworkCore
|
||||
{
|
||||
opts.WaitDelay = options.WaitDelay;
|
||||
opts.Logger = options.Logger;
|
||||
}, cancellationToken);
|
||||
}, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
var connection = database.GetDbConnection();
|
||||
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 await connection.Migrate(options, cancellationToken);
|
||||
return await connection.Migrate(options, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
finally
|
||||
{
|
||||
await connection.CloseAsync();
|
||||
await connection.CloseAsync().ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -87,7 +88,7 @@ namespace Microsoft.EntityFrameworkCore
|
||||
{
|
||||
try
|
||||
{
|
||||
await connection.OpenAsync(cancellationToken);
|
||||
await connection.OpenAsync(cancellationToken).ConfigureAwait(false);
|
||||
options.Logger?.LogInformation("Database connection available");
|
||||
return;
|
||||
}
|
||||
@@ -96,7 +97,7 @@ namespace Microsoft.EntityFrameworkCore
|
||||
// keep things quiet
|
||||
try
|
||||
{
|
||||
await Task.Delay(options.WaitDelay, cancellationToken);
|
||||
await Task.Delay(options.WaitDelay, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
catch (OperationCanceledException) when (cancellationToken.IsCancellationRequested)
|
||||
{
|
||||
@@ -109,7 +110,7 @@ namespace Microsoft.EntityFrameworkCore
|
||||
}
|
||||
finally
|
||||
{
|
||||
await connection.CloseAsync();
|
||||
await connection.CloseAsync().ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -192,7 +193,7 @@ BEGIN
|
||||
END;"
|
||||
};
|
||||
|
||||
await command.ExecuteNonQueryAsync(cancellationToken);
|
||||
await command.ExecuteNonQueryAsync(cancellationToken).ConfigureAwait(false);
|
||||
return true;
|
||||
}
|
||||
catch (Exception ex)
|
||||
@@ -235,9 +236,9 @@ END;"
|
||||
DatabaseProvider.SQLServer => $"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));
|
||||
}
|
||||
|
||||
@@ -246,7 +247,7 @@ END;"
|
||||
{
|
||||
// remove path including the separator
|
||||
string fileName = migrationFile.Replace(options.Path, "")[1..];
|
||||
using var transaction = await connection.BeginTransactionAsync(cancellationToken);
|
||||
using var transaction = await connection.BeginTransactionAsync(cancellationToken).ConfigureAwait(false);
|
||||
try
|
||||
{
|
||||
// max length in the database: 250 chars
|
||||
@@ -263,13 +264,13 @@ END;"
|
||||
string sqlScript = null;
|
||||
if (options.SourceAssembly == null)
|
||||
{
|
||||
sqlScript = await File.ReadAllTextAsync(migrationFile, cancellationToken);
|
||||
sqlScript = await File.ReadAllTextAsync(migrationFile, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
else
|
||||
{
|
||||
using var stream = options.SourceAssembly.GetManifestResourceStream(migrationFile);
|
||||
using var sr = new StreamReader(stream);
|
||||
sqlScript = await sr.ReadToEndAsync();
|
||||
sqlScript = await sr.ReadToEndAsync().ConfigureAwait(false);
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(sqlScript))
|
||||
@@ -278,7 +279,7 @@ END;"
|
||||
options.Logger?.LogDebug($" Migrating file '{fileName}' started");
|
||||
command.Transaction = transaction;
|
||||
|
||||
await command.ExecuteScript(sqlScript, cancellationToken);
|
||||
await command.ExecuteScript(sqlScript, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
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}');",
|
||||
_ => $@"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;
|
||||
options.Logger?.LogDebug($" Migrating file '{fileName}' successful");
|
||||
}
|
||||
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}");
|
||||
return false;
|
||||
}
|
||||
@@ -330,7 +331,7 @@ END;"
|
||||
if (!string.IsNullOrWhiteSpace(pt))
|
||||
{
|
||||
command.CommandText = pt;
|
||||
affectedRows += await command.ExecuteNonQueryAsync(cancellationToken);
|
||||
affectedRows += await command.ExecuteNonQueryAsync(cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
return affectedRows;
|
||||
@@ -338,7 +339,7 @@ END;"
|
||||
else
|
||||
{
|
||||
command.CommandText = text;
|
||||
return await command.ExecuteNonQueryAsync(cancellationToken);
|
||||
return await command.ExecuteNonQueryAsync(cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -3,9 +3,7 @@ using System.Reflection;
|
||||
using System.Text;
|
||||
using AMWD.Common.EntityFrameworkCore.Attributes;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
#if NET6_0_OR_GREATER
|
||||
using Microsoft.EntityFrameworkCore.Metadata;
|
||||
#endif
|
||||
|
||||
namespace AMWD.Common.EntityFrameworkCore.Extensions
|
||||
{
|
||||
@@ -35,13 +33,7 @@ namespace AMWD.Common.EntityFrameworkCore.Extensions
|
||||
index.IsUnique = indexAttribute.IsUnique;
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(indexAttribute.Name))
|
||||
{
|
||||
#if NET6_0_OR_GREATER
|
||||
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
|
||||
if ((entityType.ClrType.GetCustomAttribute(typeof(TableAttribute), false) as TableAttribute) == null)
|
||||
{
|
||||
#if NET6_0_OR_GREATER
|
||||
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());
|
||||
#endif
|
||||
foreach (var property in entityType.GetProperties())
|
||||
{
|
||||
// skip conversion when column name is explicitly set
|
||||
if ((entityType.ClrType.GetProperty(property.Name)?.GetCustomAttribute(typeof(ColumnAttribute), false) as ColumnAttribute) == null)
|
||||
{
|
||||
#if NET6_0_OR_GREATER
|
||||
property.SetColumnName(ConvertToSnakeCase(property.GetColumnName(identifier)));
|
||||
#else
|
||||
property.SetColumnName(ConvertToSnakeCase(property.GetColumnName()));
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
#if NET6_0_OR_GREATER
|
||||
|
||||
using System;
|
||||
using System;
|
||||
using AMWD.Common.EntityFrameworkCore.Converters;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
@@ -12,14 +10,14 @@ namespace AMWD.Common.EntityFrameworkCore.Extensions
|
||||
public static class ModelConfigurationBuilderExtensions
|
||||
{
|
||||
/// <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>
|
||||
/// <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 AddDateOnlyTimeOnlyConverters(this ModelConfigurationBuilder builder)
|
||||
public static ModelConfigurationBuilder AddDateOnlyConverters(this ModelConfigurationBuilder builder)
|
||||
{
|
||||
builder.Properties<DateOnly>()
|
||||
.HaveConversion<DateOnlyConverter>()
|
||||
@@ -28,6 +26,19 @@ namespace AMWD.Common.EntityFrameworkCore.Extensions
|
||||
.HaveConversion<NullableDateOnlyConverter>()
|
||||
.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>()
|
||||
.HaveConversion<TimeOnlyConverter>()
|
||||
.HaveColumnType("time");
|
||||
@@ -39,5 +50,3 @@ namespace AMWD.Common.EntityFrameworkCore.Extensions
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
3
AMWD.Common.EntityFrameworkCore/GlobalSuppressions.cs
Normal file
3
AMWD.Common.EntityFrameworkCore/GlobalSuppressions.cs
Normal file
@@ -0,0 +1,3 @@
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
|
||||
[assembly: SuppressMessage("Usage", "CA2254")]
|
||||
@@ -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>
|
||||
29
AMWD.Common.Test/AMWD.Common.Test.csproj
Normal file
29
AMWD.Common.Test/AMWD.Common.Test.csproj
Normal 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>
|
||||
@@ -8,7 +8,7 @@ using System.Threading.Tasks;
|
||||
using Moq;
|
||||
using Moq.Protected;
|
||||
|
||||
namespace AMWD.Common.Moq
|
||||
namespace AMWD.Common.Test
|
||||
{
|
||||
/// <summary>
|
||||
/// Wrapps the <see cref="Mock{HttpMessageHandler}"/> including the setup.
|
||||
@@ -39,8 +39,8 @@ namespace AMWD.Common.Moq
|
||||
|
||||
if (req.Content != null)
|
||||
{
|
||||
callback.ContentBytes = await req.Content.ReadAsByteArrayAsync();
|
||||
callback.ContentString = await req.Content.ReadAsStringAsync();
|
||||
callback.ContentBytes = await req.Content.ReadAsByteArrayAsync().ConfigureAwait(false);
|
||||
callback.ContentString = await req.Content.ReadAsStringAsync().ConfigureAwait(false);
|
||||
}
|
||||
|
||||
Callbacks.Add(callback);
|
||||
87
AMWD.Common.Test/SnapshotAssert.cs
Normal file
87
AMWD.Common.Test/SnapshotAssert.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -5,14 +5,14 @@ using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Moq;
|
||||
|
||||
namespace AMWD.Common.Moq
|
||||
namespace AMWD.Common.Test
|
||||
{
|
||||
/// <summary>
|
||||
/// Wrapps the <see cref="Mock{TcpClient}"/> including the setup.
|
||||
/// </summary>
|
||||
public class TcpClientMoq
|
||||
{
|
||||
private readonly Mock<NetworkStream> streamMock;
|
||||
private readonly Mock<NetworkStream> _streamMock;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="TcpClientMoq"/> class.
|
||||
@@ -22,8 +22,8 @@ namespace AMWD.Common.Moq
|
||||
Callbacks = new();
|
||||
Response = new byte[0];
|
||||
|
||||
streamMock = new();
|
||||
streamMock
|
||||
_streamMock = new();
|
||||
_streamMock
|
||||
.Setup(s => s.WriteAsync(It.IsAny<byte[]>(), It.IsAny<int>(), It.IsAny<int>(), It.IsAny<CancellationToken>()))
|
||||
.Callback<byte[], int, int, CancellationToken>((buffer, offset, count, _) =>
|
||||
{
|
||||
@@ -39,7 +39,7 @@ namespace AMWD.Common.Moq
|
||||
Callbacks.Add(callback);
|
||||
})
|
||||
.Returns(Task.CompletedTask);
|
||||
streamMock
|
||||
_streamMock
|
||||
.Setup(s => s.Write(It.IsAny<byte[]>(), It.IsAny<int>(), It.IsAny<int>()))
|
||||
.Callback<byte[], int, int>((buffer, offset, count) =>
|
||||
{
|
||||
@@ -55,7 +55,7 @@ namespace AMWD.Common.Moq
|
||||
Callbacks.Add(callback);
|
||||
});
|
||||
|
||||
streamMock
|
||||
_streamMock
|
||||
.Setup(s => s.ReadAsync(It.IsAny<byte[]>(), It.IsAny<int>(), It.IsAny<int>(), It.IsAny<CancellationToken>()))
|
||||
.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));
|
||||
})
|
||||
.ReturnsAsync(Response?.Length ?? 0);
|
||||
streamMock
|
||||
_streamMock
|
||||
.Setup(s => s.Read(It.IsAny<byte[]>(), It.IsAny<int>(), It.IsAny<int>()))
|
||||
.Callback<byte[], int, int>((buffer, offset, count) =>
|
||||
{
|
||||
@@ -75,7 +75,7 @@ namespace AMWD.Common.Moq
|
||||
Mock = new();
|
||||
Mock
|
||||
.Setup(c => c.GetStream())
|
||||
.Returns(streamMock.Object);
|
||||
.Returns(_streamMock.Object);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -107,28 +107,28 @@ namespace AMWD.Common.Moq
|
||||
/// </summary>
|
||||
/// <param name="times">Number of calls.</param>
|
||||
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>
|
||||
/// Verifies the number of calls writing synchronous to the stream.
|
||||
/// </summary>
|
||||
/// <param name="times">Number of calls.</param>
|
||||
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>
|
||||
/// Verifies the number of calls reading asynchronous from the stream.
|
||||
/// </summary>
|
||||
/// <param name="times">Number of calls.</param>
|
||||
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>
|
||||
/// Verifies the number of calls reading synchronous from the stream.
|
||||
/// </summary>
|
||||
/// <param name="times">Number of calls.</param>
|
||||
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>
|
||||
/// Represents the placed TCP request.
|
||||
@@ -1,7 +1,6 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<Configurations>Debug;Release;DebugLocal</Configurations>
|
||||
<TargetFramework>netstandard2.0</TargetFramework>
|
||||
<LangVersion>10.0</LangVersion>
|
||||
|
||||
@@ -11,32 +10,21 @@
|
||||
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
|
||||
<PackageId>AMWD.Common</PackageId>
|
||||
<PackageIcon>icon.png</PackageIcon>
|
||||
<PackageReadmeFile>README.md</PackageReadmeFile>
|
||||
|
||||
<Product>AM.WD Common Library</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="/" />
|
||||
<None Include="../README.md" Pack="true" PackagePath="/" />
|
||||
</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="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.NetRevisionTask" Version="0.4.3">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
||||
36
AMWD.Common/Cli/Argument.cs
Normal file
36
AMWD.Common/Cli/Argument.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
366
AMWD.Common/Cli/CommandLineParser.cs
Normal file
366
AMWD.Common/Cli/CommandLineParser.cs
Normal 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
|
||||
}
|
||||
}
|
||||
58
AMWD.Common/Cli/EnumerableWalker.cs
Normal file
58
AMWD.Common/Cli/EnumerableWalker.cs
Normal 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
113
AMWD.Common/Cli/Option.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -17,7 +17,7 @@ namespace Newtonsoft.Json
|
||||
/// <summary>
|
||||
/// Common JSON serializer settings.
|
||||
/// </summary>
|
||||
private static readonly JsonSerializerSettings jsonSerializerSettings = new()
|
||||
private static readonly JsonSerializerSettings _jsonSerializerSettings = new()
|
||||
{
|
||||
ContractResolver = new CamelCasePropertyNamesContractResolver(),
|
||||
Culture = CultureInfo.InvariantCulture
|
||||
@@ -32,7 +32,7 @@ namespace Newtonsoft.Json
|
||||
public static void DeserializeJson<T>(this T target, string json)
|
||||
{
|
||||
if (!string.IsNullOrWhiteSpace(json))
|
||||
JsonConvert.PopulateObject(json, target, jsonSerializerSettings);
|
||||
JsonConvert.PopulateObject(json, target, _jsonSerializerSettings);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -42,7 +42,7 @@ namespace Newtonsoft.Json
|
||||
/// <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>
|
||||
public static T DeserializeJson<T>(this string json)
|
||||
=> JsonConvert.DeserializeObject<T>(json, jsonSerializerSettings);
|
||||
=> JsonConvert.DeserializeObject<T>(json, _jsonSerializerSettings);
|
||||
|
||||
/// <summary>
|
||||
/// Deserializes a JSON string into a new instance or using the fallback value.
|
||||
@@ -55,7 +55,7 @@ namespace Newtonsoft.Json
|
||||
{
|
||||
try
|
||||
{
|
||||
return JsonConvert.DeserializeObject<T>(json, jsonSerializerSettings);
|
||||
return JsonConvert.DeserializeObject<T>(json, _jsonSerializerSettings);
|
||||
}
|
||||
catch
|
||||
{
|
||||
@@ -83,7 +83,7 @@ namespace Newtonsoft.Json
|
||||
jw.QuoteChar = '\'';
|
||||
|
||||
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) =>
|
||||
{
|
||||
@@ -107,7 +107,7 @@ namespace Newtonsoft.Json
|
||||
if (obj == null)
|
||||
return null;
|
||||
|
||||
var serializer = JsonSerializer.Create(jsonSerializerSettings);
|
||||
var serializer = JsonSerializer.Create(_jsonSerializerSettings);
|
||||
return JObject.FromObject(obj, serializer);
|
||||
}
|
||||
|
||||
@@ -121,7 +121,7 @@ namespace Newtonsoft.Json
|
||||
if (array == null)
|
||||
return null;
|
||||
|
||||
var serializer = JsonSerializer.Create(jsonSerializerSettings);
|
||||
var serializer = JsonSerializer.Create(_jsonSerializerSettings);
|
||||
return JArray.FromObject(array, serializer);
|
||||
}
|
||||
|
||||
@@ -146,13 +146,26 @@ namespace Newtonsoft.Json
|
||||
if (lvlObj == null)
|
||||
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)
|
||||
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>
|
||||
|
||||
@@ -58,30 +58,30 @@
|
||||
|
||||
private struct DisposableReadWriteLock : IDisposable
|
||||
{
|
||||
private readonly ReaderWriterLockSlim rwLock;
|
||||
private LockMode lockMode;
|
||||
private readonly ReaderWriterLockSlim _rwLock;
|
||||
private LockMode _lockMode;
|
||||
|
||||
public DisposableReadWriteLock(ReaderWriterLockSlim rwLock, LockMode lockMode)
|
||||
{
|
||||
this.rwLock = rwLock;
|
||||
this.lockMode = lockMode;
|
||||
_rwLock = rwLock;
|
||||
_lockMode = lockMode;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (lockMode == LockMode.Read)
|
||||
rwLock.ExitReadLock();
|
||||
if (_lockMode == LockMode.Read)
|
||||
_rwLock.ExitReadLock();
|
||||
|
||||
if (lockMode == LockMode.Upgradable && rwLock.IsWriteLockHeld) // Upgraded with EnterWriteLock alone
|
||||
rwLock.ExitWriteLock();
|
||||
if (_lockMode == LockMode.Upgradable && _rwLock.IsWriteLockHeld) // Upgraded with EnterWriteLock alone
|
||||
_rwLock.ExitWriteLock();
|
||||
|
||||
if (lockMode == LockMode.Upgradable)
|
||||
rwLock.ExitUpgradeableReadLock();
|
||||
if (_lockMode == LockMode.Upgradable)
|
||||
_rwLock.ExitUpgradeableReadLock();
|
||||
|
||||
if (lockMode == LockMode.Write)
|
||||
rwLock.ExitWriteLock();
|
||||
if (_lockMode == LockMode.Write)
|
||||
_rwLock.ExitWriteLock();
|
||||
|
||||
lockMode = LockMode.None;
|
||||
_lockMode = LockMode.None;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -20,11 +20,8 @@ namespace System.IO
|
||||
/// <returns></returns>
|
||||
public static string ReadLine(this Stream stream, Encoding encoding = null, char? eol = null)
|
||||
{
|
||||
if (encoding == null)
|
||||
encoding = Encoding.Default;
|
||||
|
||||
if (eol == null)
|
||||
eol = Environment.NewLine.Last();
|
||||
encoding ??= Encoding.Default;
|
||||
eol ??= Environment.NewLine.Last();
|
||||
|
||||
if (!stream.CanRead)
|
||||
return null;
|
||||
@@ -56,11 +53,8 @@ namespace System.IO
|
||||
/// <returns></returns>
|
||||
public static async Task<string> ReadLineAsync(this Stream stream, Encoding encoding = null, char? eol = null, CancellationToken cancellationToken = default)
|
||||
{
|
||||
if (encoding == null)
|
||||
encoding = Encoding.Default;
|
||||
|
||||
if (eol == null)
|
||||
eol = Environment.NewLine.Last();
|
||||
encoding ??= Encoding.Default;
|
||||
eol ??= Environment.NewLine.Last();
|
||||
|
||||
if (!stream.CanRead)
|
||||
return null;
|
||||
|
||||
@@ -177,10 +177,7 @@ namespace System
|
||||
|
||||
if (isValid && nameservers?.Any() == true)
|
||||
{
|
||||
var dnsClientType = Type.GetType("DNS.Client.DnsClient, DNS");
|
||||
if (dnsClientType == null)
|
||||
throw new DllNotFoundException("The DNS NuGet package is required: https://www.nuget.org/packages/DNS/7.0.0");
|
||||
|
||||
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");
|
||||
var recordTypeType = Type.GetType("DNS.Protocol.RecordType, DNS");
|
||||
var resolveMethodInfo = dnsClientType.GetMethod("Resolve", new[] { typeof(string), recordTypeType, typeof(CancellationToken) });
|
||||
|
||||
|
||||
@@ -24,13 +24,13 @@ namespace AMWD.Common.Logging
|
||||
{
|
||||
#region Fields
|
||||
|
||||
private bool isDisposed = false;
|
||||
private readonly CancellationTokenSource cancellationTokenSource = new();
|
||||
private bool _isDisposed = false;
|
||||
private readonly CancellationTokenSource _cancellationTokenSource = new();
|
||||
|
||||
private readonly StreamWriter fileWriter;
|
||||
private readonly Task writeTask;
|
||||
private readonly StreamWriter _fileWriter;
|
||||
private readonly Task _writeTask;
|
||||
|
||||
private readonly AsyncQueue<QueueItem> queue = new();
|
||||
private readonly AsyncQueue<QueueItem> _queue = new();
|
||||
|
||||
#endregion Fields
|
||||
|
||||
@@ -45,8 +45,8 @@ namespace AMWD.Common.Logging
|
||||
public FileLogger(string file, bool append = false, Encoding encoding = null)
|
||||
{
|
||||
FileName = file;
|
||||
fileWriter = new StreamWriter(FileName, append, encoding ?? Encoding.UTF8);
|
||||
writeTask = Task.Run(() => WriteFileAsync(cancellationTokenSource.Token));
|
||||
_fileWriter = new StreamWriter(FileName, append, encoding ?? Encoding.UTF8);
|
||||
_writeTask = Task.Run(() => WriteFileAsync(_cancellationTokenSource.Token));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -148,7 +148,7 @@ namespace AMWD.Common.Logging
|
||||
/// <inheritdoc cref="ILogger.BeginScope{TState}(TState)" />
|
||||
public IDisposable BeginScope<TState>(TState state)
|
||||
{
|
||||
if (isDisposed)
|
||||
if (_isDisposed)
|
||||
throw new ObjectDisposedException(GetType().FullName);
|
||||
|
||||
return ScopeProvider?.Push(state) ?? NullScope.Instance;
|
||||
@@ -157,7 +157,7 @@ namespace AMWD.Common.Logging
|
||||
/// <inheritdoc cref="ILogger.IsEnabled(LogLevel)" />
|
||||
public bool IsEnabled(LogLevel logLevel)
|
||||
{
|
||||
if (isDisposed)
|
||||
if (_isDisposed)
|
||||
throw new ObjectDisposedException(GetType().FullName);
|
||||
|
||||
return logLevel >= MinLevel;
|
||||
@@ -166,7 +166,7 @@ namespace AMWD.Common.Logging
|
||||
/// <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)
|
||||
{
|
||||
if (isDisposed)
|
||||
if (_isDisposed)
|
||||
throw new ObjectDisposedException(GetType().FullName);
|
||||
|
||||
if (!IsEnabled(logLevel))
|
||||
@@ -197,13 +197,13 @@ namespace AMWD.Common.Logging
|
||||
/// <inheritdoc cref="IDisposable.Dispose" />
|
||||
public void Dispose()
|
||||
{
|
||||
if (!isDisposed)
|
||||
if (!_isDisposed)
|
||||
{
|
||||
isDisposed = true;
|
||||
_isDisposed = true;
|
||||
|
||||
cancellationTokenSource.Cancel();
|
||||
writeTask.GetAwaiter().GetResult();
|
||||
fileWriter.Dispose();
|
||||
_cancellationTokenSource.Cancel();
|
||||
_writeTask.GetAwaiter().GetResult();
|
||||
_fileWriter.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -213,7 +213,7 @@ namespace AMWD.Common.Logging
|
||||
|
||||
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,
|
||||
Name = name,
|
||||
@@ -236,7 +236,7 @@ namespace AMWD.Common.Logging
|
||||
QueueItem[] items;
|
||||
try
|
||||
{
|
||||
items = await queue.DequeueAvailableAsync(cancellationToken: token);
|
||||
items = await _queue.DequeueAvailableAsync(cancellationToken: token).ConfigureAwait(false);
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
@@ -302,10 +302,10 @@ namespace AMWD.Common.Logging
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -12,10 +12,10 @@ namespace System.Collections.Generic
|
||||
{
|
||||
#region Fields
|
||||
|
||||
private readonly Queue<T> queue;
|
||||
private readonly Queue<T> _queue;
|
||||
|
||||
private TaskCompletionSource<bool> dequeueTcs = new();
|
||||
private TaskCompletionSource<bool> availableTcs = new();
|
||||
private TaskCompletionSource<bool> _dequeueTcs = new();
|
||||
private TaskCompletionSource<bool> _availableTcs = new();
|
||||
|
||||
#endregion Fields
|
||||
|
||||
@@ -27,7 +27,7 @@ namespace System.Collections.Generic
|
||||
[ExcludeFromCodeCoverage]
|
||||
public AsyncQueue()
|
||||
{
|
||||
queue = new Queue<T>();
|
||||
_queue = new Queue<T>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -40,7 +40,7 @@ namespace System.Collections.Generic
|
||||
[ExcludeFromCodeCoverage]
|
||||
public AsyncQueue(IEnumerable<T> collection)
|
||||
{
|
||||
queue = new Queue<T>();
|
||||
_queue = new Queue<T>();
|
||||
Enqueue(collection);
|
||||
}
|
||||
|
||||
@@ -52,7 +52,7 @@ namespace System.Collections.Generic
|
||||
[ExcludeFromCodeCoverage]
|
||||
public AsyncQueue(int capacity)
|
||||
{
|
||||
queue = new Queue<T>(capacity);
|
||||
_queue = new Queue<T>(capacity);
|
||||
}
|
||||
|
||||
#endregion Constructors
|
||||
@@ -67,9 +67,9 @@ namespace System.Collections.Generic
|
||||
{
|
||||
get
|
||||
{
|
||||
lock (queue)
|
||||
lock (_queue)
|
||||
{
|
||||
return queue.Count;
|
||||
return _queue.Count;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -84,9 +84,9 @@ namespace System.Collections.Generic
|
||||
[ExcludeFromCodeCoverage]
|
||||
public void Clear()
|
||||
{
|
||||
lock (queue)
|
||||
lock (_queue)
|
||||
{
|
||||
queue.Clear();
|
||||
_queue.Clear();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -98,9 +98,9 @@ namespace System.Collections.Generic
|
||||
[ExcludeFromCodeCoverage]
|
||||
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]
|
||||
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]
|
||||
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>
|
||||
public void Enqueue(T item)
|
||||
{
|
||||
lock (queue)
|
||||
lock (_queue)
|
||||
{
|
||||
queue.Enqueue(item);
|
||||
SetToken(dequeueTcs);
|
||||
SetToken(availableTcs);
|
||||
_queue.Enqueue(item);
|
||||
SetToken(_dequeueTcs);
|
||||
SetToken(_availableTcs);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -157,9 +157,9 @@ namespace System.Collections.Generic
|
||||
[ExcludeFromCodeCoverage]
|
||||
public T Peek()
|
||||
{
|
||||
lock (queue)
|
||||
lock (_queue)
|
||||
{
|
||||
return queue.Peek();
|
||||
return _queue.Peek();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -170,9 +170,9 @@ namespace System.Collections.Generic
|
||||
[ExcludeFromCodeCoverage]
|
||||
public T[] ToArray()
|
||||
{
|
||||
lock (queue)
|
||||
lock (_queue)
|
||||
{
|
||||
return queue.ToArray();
|
||||
return _queue.ToArray();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -182,9 +182,9 @@ namespace System.Collections.Generic
|
||||
[ExcludeFromCodeCoverage]
|
||||
public void TrimExcess()
|
||||
{
|
||||
lock (queue)
|
||||
lock (_queue)
|
||||
{
|
||||
queue.TrimExcess();
|
||||
_queue.TrimExcess();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -203,21 +203,21 @@ namespace System.Collections.Generic
|
||||
while (true)
|
||||
{
|
||||
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)
|
||||
count = maxCount;
|
||||
|
||||
var items = new T[count];
|
||||
for (int i = 0; i < count; i++)
|
||||
items[i] = queue.Dequeue();
|
||||
items[i] = _queue.Dequeue();
|
||||
|
||||
return items;
|
||||
}
|
||||
internalDequeueTcs = ResetToken(ref dequeueTcs);
|
||||
internalDequeueTcs = ResetToken(ref _dequeueTcs);
|
||||
}
|
||||
|
||||
await WaitAsync(internalDequeueTcs, cancellationToken).ConfigureAwait(false);
|
||||
@@ -238,17 +238,17 @@ namespace System.Collections.Generic
|
||||
while (true)
|
||||
{
|
||||
TaskCompletionSource<bool> internalDequeueTcs;
|
||||
lock (queue)
|
||||
lock (_queue)
|
||||
{
|
||||
if (count <= queue.Count)
|
||||
if (count <= _queue.Count)
|
||||
{
|
||||
var items = new T[count];
|
||||
for (int i = 0; i < count; i++)
|
||||
items[i] = queue.Dequeue();
|
||||
items[i] = _queue.Dequeue();
|
||||
|
||||
return items;
|
||||
}
|
||||
internalDequeueTcs = ResetToken(ref dequeueTcs);
|
||||
internalDequeueTcs = ResetToken(ref _dequeueTcs);
|
||||
}
|
||||
|
||||
await WaitAsync(internalDequeueTcs, cancellationToken).ConfigureAwait(false);
|
||||
@@ -265,12 +265,12 @@ namespace System.Collections.Generic
|
||||
while (true)
|
||||
{
|
||||
TaskCompletionSource<bool> internalDequeueTcs;
|
||||
lock (queue)
|
||||
lock (_queue)
|
||||
{
|
||||
if (queue.Count > 0)
|
||||
return queue.Dequeue();
|
||||
if (_queue.Count > 0)
|
||||
return _queue.Dequeue();
|
||||
|
||||
internalDequeueTcs = ResetToken(ref dequeueTcs);
|
||||
internalDequeueTcs = ResetToken(ref _dequeueTcs);
|
||||
}
|
||||
|
||||
await WaitAsync(internalDequeueTcs, cancellationToken).ConfigureAwait(false);
|
||||
@@ -285,12 +285,12 @@ namespace System.Collections.Generic
|
||||
public async Task WaitAsync(CancellationToken cancellationToken = default)
|
||||
{
|
||||
TaskCompletionSource<bool> internalAvailableTcs;
|
||||
lock (queue)
|
||||
lock (_queue)
|
||||
{
|
||||
if (queue.Count > 0)
|
||||
if (_queue.Count > 0)
|
||||
return;
|
||||
|
||||
internalAvailableTcs = ResetToken(ref availableTcs);
|
||||
internalAvailableTcs = ResetToken(ref _availableTcs);
|
||||
}
|
||||
|
||||
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>
|
||||
public bool Remove(T item)
|
||||
{
|
||||
lock (queue)
|
||||
lock (_queue)
|
||||
{
|
||||
var copy = new Queue<T>(queue);
|
||||
queue.Clear();
|
||||
var copy = new Queue<T>(_queue);
|
||||
_queue.Clear();
|
||||
|
||||
bool found = false;
|
||||
int count = copy.Count;
|
||||
@@ -359,7 +359,7 @@ namespace System.Collections.Generic
|
||||
var element = copy.Dequeue();
|
||||
if (found)
|
||||
{
|
||||
queue.Enqueue(element);
|
||||
_queue.Enqueue(element);
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -369,7 +369,7 @@ namespace System.Collections.Generic
|
||||
continue;
|
||||
}
|
||||
|
||||
queue.Enqueue(element);
|
||||
_queue.Enqueue(element);
|
||||
}
|
||||
|
||||
return found;
|
||||
@@ -382,19 +382,19 @@ namespace System.Collections.Generic
|
||||
/// <param name="collection">The objects to add to the <see cref="AsyncQueue{T}"/>.</param>
|
||||
public void Enqueue(IEnumerable<T> collection)
|
||||
{
|
||||
lock (queue)
|
||||
lock (_queue)
|
||||
{
|
||||
bool hasElements = false;
|
||||
foreach (var element in collection)
|
||||
{
|
||||
hasElements = true;
|
||||
queue.Enqueue(element);
|
||||
_queue.Enqueue(element);
|
||||
}
|
||||
|
||||
if (hasElements)
|
||||
{
|
||||
SetToken(dequeueTcs);
|
||||
SetToken(availableTcs);
|
||||
SetToken(_dequeueTcs);
|
||||
SetToken(_availableTcs);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -425,7 +425,7 @@ namespace System.Collections.Generic
|
||||
{
|
||||
if (await Task.WhenAny(tcs.Task, Task.Delay(-1, cancellationToken)) == tcs.Task)
|
||||
{
|
||||
await tcs.Task;
|
||||
await tcs.Task.ConfigureAwait(false);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -8,9 +8,12 @@ namespace System.Security.Cryptography
|
||||
/// </summary>
|
||||
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>
|
||||
/// 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>
|
||||
public CryptographyHelper(string keyFile = null)
|
||||
{
|
||||
masterKeyFile = keyFile;
|
||||
_masterKeyFile = keyFile;
|
||||
|
||||
if (string.IsNullOrWhiteSpace(masterKeyFile))
|
||||
masterKeyFile = "crypto.key";
|
||||
if (string.IsNullOrWhiteSpace(_masterKeyFile))
|
||||
_masterKeyFile = "crypto.key";
|
||||
|
||||
if (!Path.IsPathRooted(masterKeyFile))
|
||||
masterKeyFile = Path.Combine(AppContext.BaseDirectory, masterKeyFile);
|
||||
if (!Path.IsPathRooted(_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))
|
||||
File.WriteAllText(masterKeyFile, GetRandomString(64));
|
||||
File.WriteAllText(_masterKeyFile, GetRandomString(64));
|
||||
}
|
||||
|
||||
#region Instance methods
|
||||
@@ -46,8 +49,7 @@ namespace System.Security.Cryptography
|
||||
/// <returns>The decrypted data.</returns>
|
||||
public byte[] DecryptAes(byte[] cipher, string password = null)
|
||||
{
|
||||
if (password == null)
|
||||
password = File.ReadAllText(masterKeyFile);
|
||||
password ??= File.ReadAllText(_masterKeyFile);
|
||||
|
||||
return AesDecrypt(cipher, password);
|
||||
}
|
||||
@@ -63,8 +65,7 @@ namespace System.Security.Cryptography
|
||||
/// <returns>The encrypted data (cipher).</returns>
|
||||
public byte[] EncryptAes(byte[] plain, string password = null)
|
||||
{
|
||||
if (password == null)
|
||||
password = File.ReadAllText(masterKeyFile);
|
||||
password ??= File.ReadAllText(_masterKeyFile);
|
||||
|
||||
return AesEncrypt(plain, password);
|
||||
}
|
||||
@@ -110,8 +111,7 @@ namespace System.Security.Cryptography
|
||||
/// <returns>The decrypted data.</returns>
|
||||
public byte[] DecryptTripleDes(byte[] cipher, string password = null)
|
||||
{
|
||||
if (password == null)
|
||||
password = File.ReadAllText(masterKeyFile);
|
||||
password ??= File.ReadAllText(_masterKeyFile);
|
||||
|
||||
return TripleDesDecrypt(cipher, password);
|
||||
}
|
||||
@@ -127,8 +127,7 @@ namespace System.Security.Cryptography
|
||||
/// <returns>The encrypted data (cipher).</returns>
|
||||
public byte[] EncryptTripleDes(byte[] plain, string password = null)
|
||||
{
|
||||
if (password == null)
|
||||
password = File.ReadAllText(masterKeyFile);
|
||||
password ??= File.ReadAllText(_masterKeyFile);
|
||||
|
||||
return TripleDesEncrypt(plain, password);
|
||||
}
|
||||
@@ -183,8 +182,8 @@ namespace System.Security.Cryptography
|
||||
/// <returns>The decrypted data.</returns>
|
||||
public static byte[] AesDecrypt(byte[] cipher, string password)
|
||||
{
|
||||
byte[] salt = new byte[saltLength];
|
||||
Array.Copy(cipher, salt, saltLength);
|
||||
byte[] salt = new byte[_saltLength];
|
||||
Array.Copy(cipher, salt, _saltLength);
|
||||
|
||||
using var gen = new Rfc2898DeriveBytes(password, salt);
|
||||
using var aes = Aes.Create();
|
||||
@@ -197,7 +196,7 @@ namespace System.Security.Cryptography
|
||||
using var ms = new MemoryStream();
|
||||
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();
|
||||
|
||||
return ms.ToArray();
|
||||
@@ -224,7 +223,7 @@ namespace System.Security.Cryptography
|
||||
/// <returns>The encrypted data (cipher).</returns>
|
||||
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 aes = Aes.Create();
|
||||
@@ -269,8 +268,8 @@ namespace System.Security.Cryptography
|
||||
/// <returns>The decrypted data.</returns>
|
||||
public static byte[] TripleDesDecrypt(byte[] cipher, string password)
|
||||
{
|
||||
byte[] salt = new byte[saltLength];
|
||||
Array.Copy(cipher, salt, saltLength);
|
||||
byte[] salt = new byte[_saltLength];
|
||||
Array.Copy(cipher, salt, _saltLength);
|
||||
|
||||
using var gen = new Rfc2898DeriveBytes(password, salt);
|
||||
using var tdes = TripleDES.Create();
|
||||
@@ -283,7 +282,7 @@ namespace System.Security.Cryptography
|
||||
using var ms = new MemoryStream();
|
||||
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();
|
||||
|
||||
return ms.ToArray();
|
||||
@@ -297,7 +296,7 @@ namespace System.Security.Cryptography
|
||||
/// <returns>The encrypted data (cipher).</returns>
|
||||
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 tdes = TripleDES.Create();
|
||||
|
||||
@@ -15,9 +15,9 @@ namespace AMWD.Common.Utilities
|
||||
{
|
||||
#region Data
|
||||
|
||||
private Timer timer;
|
||||
private Timer _timer;
|
||||
|
||||
private bool nextRunPending;
|
||||
private bool _nextRunPending;
|
||||
|
||||
/// <summary>
|
||||
/// The synchronisation object.
|
||||
@@ -130,13 +130,13 @@ namespace AMWD.Common.Utilities
|
||||
tcs = CreateTcs();
|
||||
}
|
||||
IsWaitingToRun = true;
|
||||
if (timer != null)
|
||||
if (_timer != null)
|
||||
{
|
||||
timer.Change(Delay, Timeout.InfiniteTimeSpan);
|
||||
_timer.Change(Delay, Timeout.InfiniteTimeSpan);
|
||||
}
|
||||
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)
|
||||
{
|
||||
IsWaitingToRun = false;
|
||||
nextRunPending = false;
|
||||
timer?.Dispose();
|
||||
timer = null;
|
||||
_nextRunPending = false;
|
||||
_timer?.Dispose();
|
||||
_timer = null;
|
||||
if (!IsRunning)
|
||||
{
|
||||
localTcs = tcs;
|
||||
@@ -179,13 +179,13 @@ namespace AMWD.Common.Utilities
|
||||
return false;
|
||||
}
|
||||
IsWaitingToRun = true;
|
||||
if (timer != null)
|
||||
if (_timer != null)
|
||||
{
|
||||
timer.Change(TimeSpan.Zero, Timeout.InfiniteTimeSpan);
|
||||
_timer.Change(TimeSpan.Zero, Timeout.InfiniteTimeSpan);
|
||||
}
|
||||
else
|
||||
{
|
||||
timer = new Timer(OnTimerCallback, null, TimeSpan.Zero, Timeout.InfiniteTimeSpan);
|
||||
_timer = new Timer(OnTimerCallback, null, TimeSpan.Zero, Timeout.InfiniteTimeSpan);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
@@ -256,7 +256,7 @@ namespace AMWD.Common.Utilities
|
||||
{
|
||||
tcs = CreateTcs();
|
||||
IsWaitingToRun = true;
|
||||
timer = new Timer(OnTimerCallback, null, Delay, Timeout.InfiniteTimeSpan);
|
||||
_timer = new Timer(OnTimerCallback, null, Delay, Timeout.InfiniteTimeSpan);
|
||||
return this;
|
||||
}
|
||||
|
||||
@@ -285,7 +285,7 @@ namespace AMWD.Common.Utilities
|
||||
if (IsRunning)
|
||||
{
|
||||
// Currently running, remember and do nothing for now
|
||||
nextRunPending = true;
|
||||
_nextRunPending = true;
|
||||
return;
|
||||
}
|
||||
IsRunning = true;
|
||||
@@ -308,12 +308,12 @@ namespace AMWD.Common.Utilities
|
||||
{
|
||||
runAgain = false;
|
||||
IsRunning = false;
|
||||
nextRunPending = false;
|
||||
_nextRunPending = false;
|
||||
localTcs = tcs;
|
||||
if (!IsWaitingToRun)
|
||||
{
|
||||
timer?.Dispose();
|
||||
timer = null;
|
||||
_timer?.Dispose();
|
||||
_timer = null;
|
||||
}
|
||||
}
|
||||
exceptionHandler?.Invoke(ex);
|
||||
@@ -322,16 +322,16 @@ namespace AMWD.Common.Utilities
|
||||
{
|
||||
lock (syncLock)
|
||||
{
|
||||
runAgain = nextRunPending;
|
||||
runAgain = _nextRunPending;
|
||||
IsRunning = runAgain;
|
||||
nextRunPending = false;
|
||||
_nextRunPending = false;
|
||||
if (!runAgain)
|
||||
{
|
||||
if (!IsWaitingToRun)
|
||||
{
|
||||
localTcs = tcs;
|
||||
timer?.Dispose();
|
||||
timer = null;
|
||||
_timer?.Dispose();
|
||||
_timer = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -403,19 +403,19 @@ namespace AMWD.Common.Utilities
|
||||
/// <typeparam name="TResult">The type of the result value.</typeparam>
|
||||
protected class TaskCompletionSourceWrapper<TResult> : TaskCompletionSourceWrapper
|
||||
{
|
||||
private readonly TaskCompletionSource<TResult> tcs;
|
||||
private readonly TaskCompletionSource<TResult> _tcs;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the <see cref="Task{TResult}"/> of the <see cref="TaskCompletionSource{TResult}"/>.
|
||||
/// </summary>
|
||||
public override Task Task => tcs.Task;
|
||||
public override Task Task => _tcs.Task;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="TaskCompletionSourceWrapper{TResult}"/> class.
|
||||
/// </summary>
|
||||
public TaskCompletionSourceWrapper()
|
||||
{
|
||||
tcs = new TaskCompletionSource<TResult>();
|
||||
_tcs = new TaskCompletionSource<TResult>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -424,13 +424,13 @@ namespace AMWD.Common.Utilities
|
||||
/// </summary>
|
||||
/// <param name="result">The result value to bind to this <see cref="Task{TResult}"/>.</param>
|
||||
/// <seealso cref="TaskCompletionSource{TResult}.TrySetResult(TResult)"/>
|
||||
public void TrySetResult(TResult result) => tcs.TrySetResult(result);
|
||||
public void TrySetResult(TResult result) => _tcs.TrySetResult(result);
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void TrySetException(Exception exception) => tcs.TrySetException(exception);
|
||||
public override void TrySetException(Exception exception) => _tcs.TrySetException(exception);
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void TrySetCanceled() => tcs.TrySetCanceled();
|
||||
public override void TrySetCanceled() => _tcs.TrySetCanceled();
|
||||
}
|
||||
|
||||
#endregion Internal TaskCompletionSourceWrapper classes
|
||||
|
||||
@@ -150,7 +150,7 @@ namespace AMWD.Common.Utilities
|
||||
if (addressFamily != AddressFamily.Unspecified && ipAddress.AddressFamily != addressFamily)
|
||||
return null;
|
||||
|
||||
return ipAddress;
|
||||
return ipAddress.IsIPv4MappedToIPv6 ? ipAddress.MapToIPv4() : ipAddress;
|
||||
}
|
||||
|
||||
return null;
|
||||
|
||||
124
CHANGELOG.md
124
CHANGELOG.md
@@ -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/),
|
||||
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
|
||||
|
||||
@@ -13,6 +20,121 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
- `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
|
||||
|
||||
### Added
|
||||
|
||||
15
Common.sln
15
Common.sln
@@ -15,7 +15,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{F2C7556A-99E
|
||||
EndProject
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "test", "test", "{E5DF156A-6C8B-4004-BA4C-A8DDE6FD3ECD}"
|
||||
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
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "UnitTests", "UnitTests\UnitTests.csproj", "{9469D87B-126E-4338-92E3-701F762CB54D}"
|
||||
EndProject
|
||||
@@ -31,7 +31,6 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Build", "Build", "{7196DA2B
|
||||
ProjectSection(SolutionItems) = preProject
|
||||
.gitlab-ci.yml = .gitlab-ci.yml
|
||||
directory.build.props = directory.build.props
|
||||
directory.build.targets = directory.build.targets
|
||||
EndProjectSection
|
||||
EndProject
|
||||
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
|
||||
.gitignore = .gitignore
|
||||
CodeMaid.config = CodeMaid.config
|
||||
nuget.config = nuget.config
|
||||
EndProjectSection
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
DebugLocal|Any CPU = DebugLocal|Any CPU
|
||||
Release|Any CPU = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||
{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}.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.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.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.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.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.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.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.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.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.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
|
||||
<AppendTargetFrameworkToOutputPath>true</AppendTargetFrameworkToOutputPath>
|
||||
<CopyRefAssembliesToPublishDirectory>false</CopyRefAssembliesToPublishDirectory>
|
||||
<IncludeSourceRevisionInInformationalVersion>false</IncludeSourceRevisionInInformationalVersion>
|
||||
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
||||
|
||||
<IncludeSymbols>true</IncludeSymbols>
|
||||
@@ -22,4 +23,20 @@
|
||||
<Copyright>© {copyright:2020-} AM.WD</Copyright>
|
||||
<PackageLicenseExpression>MIT</PackageLicenseExpression>
|
||||
</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>
|
||||
|
||||
@@ -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>
|
||||
@@ -8,7 +8,7 @@ To save time, they are all packed to NuGet packages.
|
||||
| AMWD.Common |  |
|
||||
| AMWD.Common.AspNetCore |  |
|
||||
| AMWD.Common.EntityFrameworkCore |  |
|
||||
| AMWD.Common.Moq |  |
|
||||
| AMWD.Common.Test |  |
|
||||
| CI / CD |   |
|
||||
|
||||
## Documentation
|
||||
@@ -22,8 +22,8 @@ Create a `nuget.config` file in your root project folder (where the `.sln` file
|
||||
```xml
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<configuration>
|
||||
<packageSources>
|
||||
<add key="AM.WD" value="https://nuget.am-wd.de/v3/index.json" />
|
||||
</packageSources>
|
||||
<packageSources>
|
||||
<add key="AM.WD" value="https://nuget.am-wd.de/v3/index.json" />
|
||||
</packageSources>
|
||||
</configuration>
|
||||
```
|
||||
|
||||
@@ -5,7 +5,7 @@ using System.Security.Claims;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using AMWD.Common.AspNetCore.BasicAuthentication;
|
||||
using AMWD.Common.AspNetCore.Security.BasicAuthentication;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
@@ -22,28 +22,28 @@ namespace UnitTests.AspNetCore.Attributes
|
||||
[System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage]
|
||||
public class BasicAuthenticationAttributeTests
|
||||
{
|
||||
private Mock<IHeaderDictionary> requestHeaderMock;
|
||||
private Mock<IHeaderDictionary> responseHeaderMock;
|
||||
private Mock<IHeaderDictionary> _requestHeaderMock;
|
||||
private Mock<IHeaderDictionary> _responseHeaderMock;
|
||||
|
||||
private Mock<HttpRequest> requestMock;
|
||||
private Mock<HttpResponse> responseMock;
|
||||
private Mock<HttpRequest> _requestMock;
|
||||
private Mock<HttpResponse> _responseMock;
|
||||
|
||||
private Mock<HttpContext> contextMock;
|
||||
private Mock<HttpContext> _contextMock;
|
||||
|
||||
private Dictionary<string, string> requestHeaders;
|
||||
private string validatorRealm;
|
||||
private ClaimsPrincipal validatorResult;
|
||||
private Dictionary<string, string> _requestHeaders;
|
||||
private string _validatorRealm;
|
||||
private ClaimsPrincipal _validatorResult;
|
||||
|
||||
private string responseHeaderAuthCallback;
|
||||
private string _responseHeaderAuthCallback;
|
||||
|
||||
[TestInitialize]
|
||||
public void InitializeTest()
|
||||
{
|
||||
requestHeaders = new Dictionary<string, string>();
|
||||
validatorRealm = null;
|
||||
validatorResult = null;
|
||||
_requestHeaders = [];
|
||||
_validatorRealm = null;
|
||||
_validatorResult = null;
|
||||
|
||||
responseHeaderAuthCallback = null;
|
||||
_responseHeaderAuthCallback = null;
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
@@ -55,7 +55,7 @@ namespace UnitTests.AspNetCore.Attributes
|
||||
Username = "user",
|
||||
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();
|
||||
|
||||
@@ -64,7 +64,7 @@ namespace UnitTests.AspNetCore.Attributes
|
||||
|
||||
// assert
|
||||
Assert.IsNull(context.Result);
|
||||
Assert.IsTrue(string.IsNullOrWhiteSpace(responseHeaderAuthCallback));
|
||||
Assert.IsTrue(string.IsNullOrWhiteSpace(_responseHeaderAuthCallback));
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
@@ -72,8 +72,8 @@ namespace UnitTests.AspNetCore.Attributes
|
||||
{
|
||||
// arrange
|
||||
var attribute = new BasicAuthenticationAttribute();
|
||||
requestHeaders.Add("Authorization", $"Basic {Convert.ToBase64String(Encoding.UTF8.GetBytes($"{attribute.Username}:{attribute.Password}"))}");
|
||||
validatorResult = new ClaimsPrincipal();
|
||||
_requestHeaders.Add("Authorization", $"Basic {Convert.ToBase64String(Encoding.UTF8.GetBytes($"{attribute.Username}:{attribute.Password}"))}");
|
||||
_validatorResult = new ClaimsPrincipal();
|
||||
|
||||
var context = GetContext(hasValidator: true);
|
||||
|
||||
@@ -82,7 +82,7 @@ namespace UnitTests.AspNetCore.Attributes
|
||||
|
||||
// assert
|
||||
Assert.IsNull(context.Result);
|
||||
Assert.IsTrue(string.IsNullOrWhiteSpace(responseHeaderAuthCallback));
|
||||
Assert.IsTrue(string.IsNullOrWhiteSpace(_responseHeaderAuthCallback));
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
@@ -101,7 +101,7 @@ namespace UnitTests.AspNetCore.Attributes
|
||||
|
||||
// assert
|
||||
Assert.IsNull(context.Result);
|
||||
Assert.IsTrue(string.IsNullOrWhiteSpace(responseHeaderAuthCallback));
|
||||
Assert.IsTrue(string.IsNullOrWhiteSpace(_responseHeaderAuthCallback));
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
@@ -123,8 +123,8 @@ namespace UnitTests.AspNetCore.Attributes
|
||||
Assert.IsTrue(context.Result is StatusCodeResult);
|
||||
Assert.AreEqual(401, ((StatusCodeResult)context.Result).StatusCode);
|
||||
|
||||
Assert.IsFalse(string.IsNullOrWhiteSpace(responseHeaderAuthCallback));
|
||||
Assert.AreEqual("Basic", responseHeaderAuthCallback);
|
||||
Assert.IsFalse(string.IsNullOrWhiteSpace(_responseHeaderAuthCallback));
|
||||
Assert.AreEqual("Basic", _responseHeaderAuthCallback);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
@@ -147,8 +147,8 @@ namespace UnitTests.AspNetCore.Attributes
|
||||
Assert.IsTrue(context.Result is StatusCodeResult);
|
||||
Assert.AreEqual(401, ((StatusCodeResult)context.Result).StatusCode);
|
||||
|
||||
Assert.IsFalse(string.IsNullOrWhiteSpace(responseHeaderAuthCallback));
|
||||
Assert.AreEqual("Basic realm=\"re:alm\"", responseHeaderAuthCallback);
|
||||
Assert.IsFalse(string.IsNullOrWhiteSpace(_responseHeaderAuthCallback));
|
||||
Assert.AreEqual("Basic realm=\"re:alm\"", _responseHeaderAuthCallback);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
@@ -160,7 +160,7 @@ namespace UnitTests.AspNetCore.Attributes
|
||||
Username = "user",
|
||||
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();
|
||||
|
||||
// act
|
||||
@@ -171,8 +171,8 @@ namespace UnitTests.AspNetCore.Attributes
|
||||
Assert.IsTrue(context.Result is StatusCodeResult);
|
||||
Assert.AreEqual(401, ((StatusCodeResult)context.Result).StatusCode);
|
||||
|
||||
Assert.IsFalse(string.IsNullOrWhiteSpace(responseHeaderAuthCallback));
|
||||
Assert.AreEqual("Basic", responseHeaderAuthCallback);
|
||||
Assert.IsFalse(string.IsNullOrWhiteSpace(_responseHeaderAuthCallback));
|
||||
Assert.AreEqual("Basic", _responseHeaderAuthCallback);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
@@ -184,7 +184,7 @@ namespace UnitTests.AspNetCore.Attributes
|
||||
Username = "user",
|
||||
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();
|
||||
|
||||
// act
|
||||
@@ -195,8 +195,8 @@ namespace UnitTests.AspNetCore.Attributes
|
||||
Assert.IsTrue(context.Result is StatusCodeResult);
|
||||
Assert.AreEqual(401, ((StatusCodeResult)context.Result).StatusCode);
|
||||
|
||||
Assert.IsFalse(string.IsNullOrWhiteSpace(responseHeaderAuthCallback));
|
||||
Assert.AreEqual("Basic", responseHeaderAuthCallback);
|
||||
Assert.IsFalse(string.IsNullOrWhiteSpace(_responseHeaderAuthCallback));
|
||||
Assert.AreEqual("Basic", _responseHeaderAuthCallback);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
@@ -207,7 +207,7 @@ namespace UnitTests.AspNetCore.Attributes
|
||||
{
|
||||
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);
|
||||
|
||||
// act
|
||||
@@ -218,17 +218,17 @@ namespace UnitTests.AspNetCore.Attributes
|
||||
Assert.IsTrue(context.Result is StatusCodeResult);
|
||||
Assert.AreEqual(401, ((StatusCodeResult)context.Result).StatusCode);
|
||||
|
||||
Assert.IsFalse(string.IsNullOrWhiteSpace(responseHeaderAuthCallback));
|
||||
Assert.AreEqual("Basic realm=\"attribute\"", responseHeaderAuthCallback);
|
||||
Assert.IsFalse(string.IsNullOrWhiteSpace(_responseHeaderAuthCallback));
|
||||
Assert.AreEqual("Basic realm=\"attribute\"", _responseHeaderAuthCallback);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public async Task ShouldAskOnValidatorWithRealmOnValidator()
|
||||
{
|
||||
// arrange
|
||||
validatorRealm = "validator";
|
||||
_validatorRealm = "validator";
|
||||
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);
|
||||
|
||||
// act
|
||||
@@ -239,8 +239,8 @@ namespace UnitTests.AspNetCore.Attributes
|
||||
Assert.IsTrue(context.Result is StatusCodeResult);
|
||||
Assert.AreEqual(401, ((StatusCodeResult)context.Result).StatusCode);
|
||||
|
||||
Assert.IsFalse(string.IsNullOrWhiteSpace(responseHeaderAuthCallback));
|
||||
Assert.AreEqual("Basic realm=\"validator\"", responseHeaderAuthCallback);
|
||||
Assert.IsFalse(string.IsNullOrWhiteSpace(_responseHeaderAuthCallback));
|
||||
Assert.AreEqual("Basic realm=\"validator\"", _responseHeaderAuthCallback);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
@@ -248,7 +248,7 @@ namespace UnitTests.AspNetCore.Attributes
|
||||
{
|
||||
// arrange
|
||||
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();
|
||||
|
||||
// act
|
||||
@@ -262,34 +262,38 @@ namespace UnitTests.AspNetCore.Attributes
|
||||
|
||||
private AuthorizationFilterContext GetContext(bool isAnonymousAllowed = false, bool hasValidator = false)
|
||||
{
|
||||
requestHeaderMock = new Mock<IHeaderDictionary>();
|
||||
foreach (var header in requestHeaders)
|
||||
_requestHeaderMock = new Mock<IHeaderDictionary>();
|
||||
foreach (var header in _requestHeaders)
|
||||
{
|
||||
requestHeaderMock
|
||||
StringValues outVal = header.Value;
|
||||
_requestHeaderMock
|
||||
.Setup(h => h.ContainsKey(header.Key))
|
||||
.Returns(true);
|
||||
requestHeaderMock
|
||||
_requestHeaderMock
|
||||
.Setup(h => h[header.Key])
|
||||
.Returns(header.Value);
|
||||
_requestHeaderMock
|
||||
.Setup(h => h.TryGetValue(header.Key, out outVal))
|
||||
.Returns(true);
|
||||
}
|
||||
|
||||
responseHeaderMock = new Mock<IHeaderDictionary>();
|
||||
responseHeaderMock
|
||||
.SetupSet(h => h["WWW-Authenticate"] = It.IsAny<StringValues>())
|
||||
.Callback<string, StringValues>((key, value) =>
|
||||
_responseHeaderMock = new Mock<IHeaderDictionary>();
|
||||
_responseHeaderMock
|
||||
.SetupSet(h => h.WWWAuthenticate = It.IsAny<StringValues>())
|
||||
.Callback<StringValues>((value) =>
|
||||
{
|
||||
responseHeaderAuthCallback = value;
|
||||
_responseHeaderAuthCallback = value;
|
||||
});
|
||||
|
||||
requestMock = new Mock<HttpRequest>();
|
||||
requestMock
|
||||
_requestMock = new Mock<HttpRequest>();
|
||||
_requestMock
|
||||
.Setup(r => r.Headers)
|
||||
.Returns(requestHeaderMock.Object);
|
||||
.Returns(_requestHeaderMock.Object);
|
||||
|
||||
responseMock = new Mock<HttpResponse>();
|
||||
responseMock
|
||||
_responseMock = new Mock<HttpResponse>();
|
||||
_responseMock
|
||||
.Setup(r => r.Headers)
|
||||
.Returns(responseHeaderMock.Object);
|
||||
.Returns(_responseHeaderMock.Object);
|
||||
|
||||
var requestServicesMock = new Mock<IServiceProvider>();
|
||||
|
||||
@@ -298,10 +302,10 @@ namespace UnitTests.AspNetCore.Attributes
|
||||
var validatorMock = new Mock<IBasicAuthenticationValidator>();
|
||||
validatorMock
|
||||
.Setup(v => v.Realm)
|
||||
.Returns(validatorRealm);
|
||||
.Returns(_validatorRealm);
|
||||
validatorMock
|
||||
.Setup(v => v.ValidateAsync(It.IsAny<string>(), It.IsAny<string>(), It.IsAny<IPAddress>(), It.IsAny<CancellationToken>()))
|
||||
.ReturnsAsync(validatorResult);
|
||||
.ReturnsAsync(_validatorResult);
|
||||
|
||||
requestServicesMock
|
||||
.Setup(rs => rs.GetService(typeof(IBasicAuthenticationValidator)))
|
||||
@@ -313,20 +317,20 @@ namespace UnitTests.AspNetCore.Attributes
|
||||
.Setup(ci => ci.RemoteIpAddress)
|
||||
.Returns(IPAddress.Loopback);
|
||||
|
||||
contextMock = new Mock<HttpContext>();
|
||||
contextMock
|
||||
_contextMock = new Mock<HttpContext>();
|
||||
_contextMock
|
||||
.Setup(c => c.Request)
|
||||
.Returns(requestMock.Object);
|
||||
contextMock
|
||||
.Returns(_requestMock.Object);
|
||||
_contextMock
|
||||
.Setup(c => c.Response)
|
||||
.Returns(responseMock.Object);
|
||||
contextMock
|
||||
.Returns(_responseMock.Object);
|
||||
_contextMock
|
||||
.Setup(c => c.RequestServices)
|
||||
.Returns(requestServicesMock.Object);
|
||||
contextMock
|
||||
_contextMock
|
||||
.Setup(c => c.Connection)
|
||||
.Returns(connectionInfoMock.Object);
|
||||
contextMock
|
||||
_contextMock
|
||||
.Setup(c => c.RequestAborted)
|
||||
.Returns(CancellationToken.None);
|
||||
|
||||
@@ -341,7 +345,7 @@ namespace UnitTests.AspNetCore.Attributes
|
||||
|
||||
return new AuthorizationFilterContext(new ActionContext
|
||||
{
|
||||
HttpContext = contextMock.Object,
|
||||
HttpContext = _contextMock.Object,
|
||||
RouteData = routeDataMock.Object,
|
||||
ActionDescriptor = actionDescriptor,
|
||||
}, new List<IFilterMetadata>());
|
||||
|
||||
@@ -14,22 +14,22 @@ namespace UnitTests.AspNetCore.Attributes
|
||||
{
|
||||
[TestClass]
|
||||
[System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage]
|
||||
public class IPWhitelistAttributeTests
|
||||
public class IPAllowListAttributeTests
|
||||
{
|
||||
private Dictionary<string, string> requestHeaders;
|
||||
private Dictionary<object, object> itemsCallback;
|
||||
private string configKey;
|
||||
private bool configExists;
|
||||
private List<string> allowedIpsConfig;
|
||||
private Dictionary<string, string> _requestHeaders;
|
||||
private Dictionary<object, object> _itemsCallback;
|
||||
private string _configKey;
|
||||
private bool _configExists;
|
||||
private List<string> _allowedIpsConfig;
|
||||
|
||||
[TestInitialize]
|
||||
public void InitializeTest()
|
||||
{
|
||||
requestHeaders = new Dictionary<string, string>();
|
||||
itemsCallback = new Dictionary<object, object>();
|
||||
configKey = null;
|
||||
configExists = false;
|
||||
allowedIpsConfig = new List<string>();
|
||||
_requestHeaders = [];
|
||||
_itemsCallback = [];
|
||||
_configKey = null;
|
||||
_configExists = false;
|
||||
_allowedIpsConfig = [];
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
@@ -37,7 +37,7 @@ namespace UnitTests.AspNetCore.Attributes
|
||||
{
|
||||
// arrange
|
||||
var remote = IPAddress.Parse("192.168.178.1");
|
||||
var attribute = new IPWhitelistAttribute();
|
||||
var attribute = new IPAllowListAttribute();
|
||||
var context = GetContext(remote);
|
||||
|
||||
// act
|
||||
@@ -48,8 +48,8 @@ namespace UnitTests.AspNetCore.Attributes
|
||||
Assert.IsTrue(context.Result is StatusCodeResult);
|
||||
Assert.AreEqual(403, ((StatusCodeResult)context.Result).StatusCode);
|
||||
|
||||
Assert.AreEqual(1, itemsCallback.Count);
|
||||
Assert.AreEqual(remote, itemsCallback["RemoteAddress"]);
|
||||
Assert.AreEqual(1, _itemsCallback.Count);
|
||||
Assert.AreEqual(remote, _itemsCallback["RemoteAddress"]);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
@@ -57,7 +57,7 @@ namespace UnitTests.AspNetCore.Attributes
|
||||
{
|
||||
// arrange
|
||||
var remote = IPAddress.Parse("192.168.178.1");
|
||||
var attribute = new IPWhitelistAttribute
|
||||
var attribute = new IPAllowListAttribute
|
||||
{
|
||||
AllowedIpAddresses = "192.168.178:1"
|
||||
};
|
||||
@@ -71,15 +71,15 @@ namespace UnitTests.AspNetCore.Attributes
|
||||
Assert.IsTrue(context.Result is StatusCodeResult);
|
||||
Assert.AreEqual(403, ((StatusCodeResult)context.Result).StatusCode);
|
||||
|
||||
Assert.AreEqual(1, itemsCallback.Count);
|
||||
Assert.AreEqual(remote, itemsCallback["RemoteAddress"]);
|
||||
Assert.AreEqual(1, _itemsCallback.Count);
|
||||
Assert.AreEqual(remote, _itemsCallback["RemoteAddress"]);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ShouldAllowLocalAccess()
|
||||
{
|
||||
// arrange
|
||||
var attribute = new IPWhitelistAttribute();
|
||||
var attribute = new IPAllowListAttribute();
|
||||
var context = GetContext();
|
||||
|
||||
// act
|
||||
@@ -87,15 +87,15 @@ namespace UnitTests.AspNetCore.Attributes
|
||||
|
||||
// assert
|
||||
Assert.IsNull(context.Result);
|
||||
Assert.AreEqual(1, itemsCallback.Count);
|
||||
Assert.AreEqual(IPAddress.Loopback, itemsCallback["RemoteAddress"]);
|
||||
Assert.AreEqual(1, _itemsCallback.Count);
|
||||
Assert.AreEqual(IPAddress.Loopback, _itemsCallback["RemoteAddress"]);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ShouldDenyLocalAccess()
|
||||
{
|
||||
// arrange
|
||||
var attribute = new IPWhitelistAttribute
|
||||
var attribute = new IPAllowListAttribute
|
||||
{
|
||||
AllowLocalAccess = false
|
||||
};
|
||||
@@ -109,8 +109,8 @@ namespace UnitTests.AspNetCore.Attributes
|
||||
Assert.IsTrue(context.Result is StatusCodeResult);
|
||||
Assert.AreEqual(403, ((StatusCodeResult)context.Result).StatusCode);
|
||||
|
||||
Assert.AreEqual(1, itemsCallback.Count);
|
||||
Assert.AreEqual(IPAddress.Loopback, itemsCallback["RemoteAddress"]);
|
||||
Assert.AreEqual(1, _itemsCallback.Count);
|
||||
Assert.AreEqual(IPAddress.Loopback, _itemsCallback["RemoteAddress"]);
|
||||
}
|
||||
|
||||
[DataTestMethod]
|
||||
@@ -120,7 +120,7 @@ namespace UnitTests.AspNetCore.Attributes
|
||||
{
|
||||
// arrange
|
||||
var remote = IPAddress.Parse(address);
|
||||
var attribute = new IPWhitelistAttribute
|
||||
var attribute = new IPAllowListAttribute
|
||||
{
|
||||
AllowLocalAccess = false,
|
||||
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(1, itemsCallback.Count);
|
||||
Assert.AreEqual(remote, itemsCallback["RemoteAddress"]);
|
||||
Assert.AreEqual(1, _itemsCallback.Count);
|
||||
Assert.AreEqual(remote, _itemsCallback["RemoteAddress"]);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ShouldAllowLocalAccessConfig()
|
||||
{
|
||||
// arrange
|
||||
configKey = "White:List";
|
||||
configExists = true;
|
||||
allowedIpsConfig.Add("127.0.0.0/8");
|
||||
allowedIpsConfig.Add("192.168.178.10");
|
||||
var attribute = new IPWhitelistAttribute
|
||||
_configKey = "White:List";
|
||||
_configExists = true;
|
||||
_allowedIpsConfig.Add("127.0.0.0/8");
|
||||
_allowedIpsConfig.Add("192.168.178.10");
|
||||
var attribute = new IPAllowListAttribute
|
||||
{
|
||||
AllowLocalAccess = true,
|
||||
ConfigurationKey = configKey
|
||||
ConfigurationKey = _configKey
|
||||
};
|
||||
var context = GetContext();
|
||||
|
||||
@@ -166,22 +166,22 @@ namespace UnitTests.AspNetCore.Attributes
|
||||
|
||||
// assert
|
||||
Assert.IsNull(context.Result);
|
||||
Assert.AreEqual(1, itemsCallback.Count);
|
||||
Assert.AreEqual(IPAddress.Loopback, itemsCallback["RemoteAddress"]);
|
||||
Assert.AreEqual(1, _itemsCallback.Count);
|
||||
Assert.AreEqual(IPAddress.Loopback, _itemsCallback["RemoteAddress"]);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ShouldDenyLocalAccessConfig()
|
||||
{
|
||||
// arrange
|
||||
configKey = "White:List";
|
||||
configExists = true;
|
||||
allowedIpsConfig.Add("");
|
||||
allowedIpsConfig.Add("192.168.178.10");
|
||||
var attribute = new IPWhitelistAttribute
|
||||
_configKey = "White:List";
|
||||
_configExists = true;
|
||||
_allowedIpsConfig.Add("");
|
||||
_allowedIpsConfig.Add("192.168.178.10");
|
||||
var attribute = new IPAllowListAttribute
|
||||
{
|
||||
AllowLocalAccess = false,
|
||||
ConfigurationKey = configKey
|
||||
ConfigurationKey = _configKey
|
||||
};
|
||||
var context = GetContext();
|
||||
|
||||
@@ -193,8 +193,8 @@ namespace UnitTests.AspNetCore.Attributes
|
||||
Assert.IsTrue(context.Result is StatusCodeResult);
|
||||
Assert.AreEqual(403, ((StatusCodeResult)context.Result).StatusCode);
|
||||
|
||||
Assert.AreEqual(1, itemsCallback.Count);
|
||||
Assert.AreEqual(IPAddress.Loopback, itemsCallback["RemoteAddress"]);
|
||||
Assert.AreEqual(1, _itemsCallback.Count);
|
||||
Assert.AreEqual(IPAddress.Loopback, _itemsCallback["RemoteAddress"]);
|
||||
}
|
||||
|
||||
[DataTestMethod]
|
||||
@@ -203,13 +203,13 @@ namespace UnitTests.AspNetCore.Attributes
|
||||
public void ShouldAllowSpecificAddressConfig(string address)
|
||||
{
|
||||
// arrange
|
||||
configKey = "White:List";
|
||||
configExists = true;
|
||||
allowedIpsConfig.Add("192.168.178.10");
|
||||
var attribute = new IPWhitelistAttribute
|
||||
_configKey = "White:List";
|
||||
_configExists = true;
|
||||
_allowedIpsConfig.Add("192.168.178.10");
|
||||
var attribute = new IPAllowListAttribute
|
||||
{
|
||||
AllowLocalAccess = false,
|
||||
ConfigurationKey = configKey
|
||||
ConfigurationKey = _configKey
|
||||
};
|
||||
var remote = IPAddress.Parse(address);
|
||||
var context = GetContext(remote);
|
||||
@@ -229,20 +229,20 @@ namespace UnitTests.AspNetCore.Attributes
|
||||
Assert.AreEqual(403, ((StatusCodeResult)context.Result).StatusCode);
|
||||
}
|
||||
|
||||
Assert.AreEqual(1, itemsCallback.Count);
|
||||
Assert.AreEqual(remote, itemsCallback["RemoteAddress"]);
|
||||
Assert.AreEqual(1, _itemsCallback.Count);
|
||||
Assert.AreEqual(remote, _itemsCallback["RemoteAddress"]);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ShouldDenyOnMissingConfiguration()
|
||||
{
|
||||
// arrange
|
||||
configKey = "White:List";
|
||||
configExists = false;
|
||||
var attribute = new IPWhitelistAttribute
|
||||
_configKey = "White:List";
|
||||
_configExists = false;
|
||||
var attribute = new IPAllowListAttribute
|
||||
{
|
||||
AllowLocalAccess = false,
|
||||
ConfigurationKey = configKey
|
||||
ConfigurationKey = _configKey
|
||||
};
|
||||
var context = GetContext();
|
||||
|
||||
@@ -254,14 +254,14 @@ namespace UnitTests.AspNetCore.Attributes
|
||||
Assert.IsTrue(context.Result is StatusCodeResult);
|
||||
Assert.AreEqual(403, ((StatusCodeResult)context.Result).StatusCode);
|
||||
|
||||
Assert.AreEqual(1, itemsCallback.Count);
|
||||
Assert.AreEqual(IPAddress.Loopback, itemsCallback["RemoteAddress"]);
|
||||
Assert.AreEqual(1, _itemsCallback.Count);
|
||||
Assert.AreEqual(IPAddress.Loopback, _itemsCallback["RemoteAddress"]);
|
||||
}
|
||||
|
||||
private ActionExecutingContext GetContext(IPAddress remote = null)
|
||||
{
|
||||
var requestHeaderMock = new Mock<IHeaderDictionary>();
|
||||
foreach (var header in requestHeaders)
|
||||
foreach (var header in _requestHeaders)
|
||||
{
|
||||
requestHeaderMock
|
||||
.Setup(h => h.ContainsKey(header.Key))
|
||||
@@ -287,11 +287,11 @@ namespace UnitTests.AspNetCore.Attributes
|
||||
var itemsMock = new Mock<IDictionary<object, object>>();
|
||||
itemsMock
|
||||
.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 children = new List<IConfigurationSection>();
|
||||
foreach (string ipAddress in allowedIpsConfig)
|
||||
foreach (string ipAddress in _allowedIpsConfig)
|
||||
{
|
||||
var csm = new Mock<IConfigurationSection>();
|
||||
csm.Setup(cs => cs.Value).Returns(ipAddress);
|
||||
@@ -305,8 +305,8 @@ namespace UnitTests.AspNetCore.Attributes
|
||||
.Returns(children);
|
||||
|
||||
configurationMock
|
||||
.Setup(c => c.GetSection(configKey))
|
||||
.Returns(configExists ? configSectionMock.Object : null);
|
||||
.Setup(c => c.GetSection(_configKey))
|
||||
.Returns(_configExists ? configSectionMock.Object : null);
|
||||
|
||||
var requestServicesMock = new Mock<IServiceProvider>();
|
||||
requestServicesMock
|
||||
@@ -14,22 +14,22 @@ namespace UnitTests.AspNetCore.Attributes
|
||||
{
|
||||
[TestClass]
|
||||
[System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage]
|
||||
public class IPBlacklistAttributeTests
|
||||
public class IPBlockListAttributeTests
|
||||
{
|
||||
private Dictionary<string, string> requestHeaders;
|
||||
private Dictionary<object, object> itemsCallback;
|
||||
private string configKey;
|
||||
private bool configExists;
|
||||
private List<string> restrictedIpsConfig;
|
||||
private Dictionary<string, string> _requestHeaders;
|
||||
private Dictionary<object, object> _itemsCallback;
|
||||
private string _configKey;
|
||||
private bool _configExists;
|
||||
private List<string> _restrictedIpsConfig;
|
||||
|
||||
[TestInitialize]
|
||||
public void InitializeTest()
|
||||
{
|
||||
requestHeaders = new Dictionary<string, string>();
|
||||
itemsCallback = new Dictionary<object, object>();
|
||||
configKey = null;
|
||||
configExists = false;
|
||||
restrictedIpsConfig = new List<string>();
|
||||
_requestHeaders = [];
|
||||
_itemsCallback = [];
|
||||
_configKey = null;
|
||||
_configExists = false;
|
||||
_restrictedIpsConfig = [];
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
@@ -37,7 +37,7 @@ namespace UnitTests.AspNetCore.Attributes
|
||||
{
|
||||
// arrange
|
||||
var remote = IPAddress.Parse("192.168.178.1");
|
||||
var attribute = new IPBlacklistAttribute();
|
||||
var attribute = new IPBlockListAttribute();
|
||||
var context = GetContext(remote);
|
||||
|
||||
// act
|
||||
@@ -45,8 +45,8 @@ namespace UnitTests.AspNetCore.Attributes
|
||||
|
||||
// assert
|
||||
Assert.IsNull(context.Result);
|
||||
Assert.AreEqual(1, itemsCallback.Count);
|
||||
Assert.AreEqual(remote, itemsCallback["RemoteAddress"]);
|
||||
Assert.AreEqual(1, _itemsCallback.Count);
|
||||
Assert.AreEqual(remote, _itemsCallback["RemoteAddress"]);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
@@ -54,9 +54,9 @@ namespace UnitTests.AspNetCore.Attributes
|
||||
{
|
||||
// arrange
|
||||
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);
|
||||
|
||||
@@ -65,18 +65,18 @@ namespace UnitTests.AspNetCore.Attributes
|
||||
|
||||
// assert
|
||||
Assert.IsNull(context.Result);
|
||||
Assert.AreEqual(1, itemsCallback.Count);
|
||||
Assert.AreEqual(remote, itemsCallback["RemoteAddress"]);
|
||||
Assert.AreEqual(1, _itemsCallback.Count);
|
||||
Assert.AreEqual(remote, _itemsCallback["RemoteAddress"]);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ShouldAllowLocalAccess()
|
||||
{
|
||||
// arrange
|
||||
var attribute = new IPBlacklistAttribute
|
||||
var attribute = new IPBlockListAttribute
|
||||
{
|
||||
RestrictLocalAccess = false,
|
||||
RestrictedIpAddresses = "127.0.0.0/8"
|
||||
BlockLocalAccess = false,
|
||||
BlockedIpAddresses = "127.0.0.0/8"
|
||||
};
|
||||
var context = GetContext();
|
||||
|
||||
@@ -85,18 +85,18 @@ namespace UnitTests.AspNetCore.Attributes
|
||||
|
||||
// assert
|
||||
Assert.IsNull(context.Result);
|
||||
Assert.AreEqual(1, itemsCallback.Count);
|
||||
Assert.AreEqual(IPAddress.Loopback, itemsCallback["RemoteAddress"]);
|
||||
Assert.AreEqual(1, _itemsCallback.Count);
|
||||
Assert.AreEqual(IPAddress.Loopback, _itemsCallback["RemoteAddress"]);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ShouldBlockLocalAccess()
|
||||
{
|
||||
// arrange
|
||||
var attribute = new IPBlacklistAttribute
|
||||
var attribute = new IPBlockListAttribute
|
||||
{
|
||||
RestrictLocalAccess = true,
|
||||
RestrictedIpAddresses = ",127.0.0.0/8"
|
||||
BlockLocalAccess = true,
|
||||
BlockedIpAddresses = ",127.0.0.0/8"
|
||||
};
|
||||
var context = GetContext();
|
||||
|
||||
@@ -108,8 +108,8 @@ namespace UnitTests.AspNetCore.Attributes
|
||||
Assert.IsTrue(context.Result is StatusCodeResult);
|
||||
Assert.AreEqual(403, ((StatusCodeResult)context.Result).StatusCode);
|
||||
|
||||
Assert.AreEqual(1, itemsCallback.Count);
|
||||
Assert.AreEqual(IPAddress.Loopback, itemsCallback["RemoteAddress"]);
|
||||
Assert.AreEqual(1, _itemsCallback.Count);
|
||||
Assert.AreEqual(IPAddress.Loopback, _itemsCallback["RemoteAddress"]);
|
||||
}
|
||||
|
||||
[DataTestMethod]
|
||||
@@ -119,10 +119,10 @@ namespace UnitTests.AspNetCore.Attributes
|
||||
{
|
||||
// arrange
|
||||
var remote = IPAddress.Parse(address);
|
||||
var attribute = new IPBlacklistAttribute
|
||||
var attribute = new IPBlockListAttribute
|
||||
{
|
||||
RestrictLocalAccess = true,
|
||||
RestrictedIpAddresses = "127.0.0.0/8,192.168.178.10"
|
||||
BlockLocalAccess = true,
|
||||
BlockedIpAddresses = "127.0.0.0/8,192.168.178.10"
|
||||
};
|
||||
var context = GetContext(remote);
|
||||
|
||||
@@ -141,22 +141,22 @@ namespace UnitTests.AspNetCore.Attributes
|
||||
Assert.IsNull(context.Result);
|
||||
}
|
||||
|
||||
Assert.AreEqual(1, itemsCallback.Count);
|
||||
Assert.AreEqual(remote, itemsCallback["RemoteAddress"]);
|
||||
Assert.AreEqual(1, _itemsCallback.Count);
|
||||
Assert.AreEqual(remote, _itemsCallback["RemoteAddress"]);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ShouldAllowLocalAccessConfig()
|
||||
{
|
||||
// arrange
|
||||
configKey = "Black:List";
|
||||
configExists = true;
|
||||
restrictedIpsConfig.Add("127.0.0.0/8");
|
||||
restrictedIpsConfig.Add("192.168.178.10");
|
||||
var attribute = new IPBlacklistAttribute
|
||||
_configKey = "Black:List";
|
||||
_configExists = true;
|
||||
_restrictedIpsConfig.Add("127.0.0.0/8");
|
||||
_restrictedIpsConfig.Add("192.168.178.10");
|
||||
var attribute = new IPBlockListAttribute
|
||||
{
|
||||
RestrictLocalAccess = false,
|
||||
ConfigurationKey = configKey
|
||||
BlockLocalAccess = false,
|
||||
ConfigurationKey = _configKey
|
||||
};
|
||||
var context = GetContext();
|
||||
|
||||
@@ -165,23 +165,23 @@ namespace UnitTests.AspNetCore.Attributes
|
||||
|
||||
// assert
|
||||
Assert.IsNull(context.Result);
|
||||
Assert.AreEqual(1, itemsCallback.Count);
|
||||
Assert.AreEqual(IPAddress.Loopback, itemsCallback["RemoteAddress"]);
|
||||
Assert.AreEqual(1, _itemsCallback.Count);
|
||||
Assert.AreEqual(IPAddress.Loopback, _itemsCallback["RemoteAddress"]);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ShouldBlockLocalAccessConfig()
|
||||
{
|
||||
// arrange
|
||||
configKey = "Black:List";
|
||||
configExists = true;
|
||||
restrictedIpsConfig.Add("");
|
||||
restrictedIpsConfig.Add("127.0.0.0/8");
|
||||
restrictedIpsConfig.Add("192.168.178.10");
|
||||
var attribute = new IPBlacklistAttribute
|
||||
_configKey = "Black:List";
|
||||
_configExists = true;
|
||||
_restrictedIpsConfig.Add("");
|
||||
_restrictedIpsConfig.Add("127.0.0.0/8");
|
||||
_restrictedIpsConfig.Add("192.168.178.10");
|
||||
var attribute = new IPBlockListAttribute
|
||||
{
|
||||
RestrictLocalAccess = true,
|
||||
ConfigurationKey = configKey
|
||||
BlockLocalAccess = true,
|
||||
ConfigurationKey = _configKey
|
||||
};
|
||||
var context = GetContext();
|
||||
|
||||
@@ -193,8 +193,8 @@ namespace UnitTests.AspNetCore.Attributes
|
||||
Assert.IsTrue(context.Result is StatusCodeResult);
|
||||
Assert.AreEqual(403, ((StatusCodeResult)context.Result).StatusCode);
|
||||
|
||||
Assert.AreEqual(1, itemsCallback.Count);
|
||||
Assert.AreEqual(IPAddress.Loopback, itemsCallback["RemoteAddress"]);
|
||||
Assert.AreEqual(1, _itemsCallback.Count);
|
||||
Assert.AreEqual(IPAddress.Loopback, _itemsCallback["RemoteAddress"]);
|
||||
}
|
||||
|
||||
[DataTestMethod]
|
||||
@@ -203,14 +203,14 @@ namespace UnitTests.AspNetCore.Attributes
|
||||
public void ShouldBlockSpecificAddressConfig(string address)
|
||||
{
|
||||
// arrange
|
||||
configKey = "Black:List";
|
||||
configExists = true;
|
||||
restrictedIpsConfig.Add("127.0.0.0/8");
|
||||
restrictedIpsConfig.Add("192.168.178.10");
|
||||
var attribute = new IPBlacklistAttribute
|
||||
_configKey = "Black:List";
|
||||
_configExists = true;
|
||||
_restrictedIpsConfig.Add("127.0.0.0/8");
|
||||
_restrictedIpsConfig.Add("192.168.178.10");
|
||||
var attribute = new IPBlockListAttribute
|
||||
{
|
||||
RestrictLocalAccess = true,
|
||||
ConfigurationKey = configKey
|
||||
BlockLocalAccess = true,
|
||||
ConfigurationKey = _configKey
|
||||
};
|
||||
var remote = IPAddress.Parse(address);
|
||||
var context = GetContext(remote);
|
||||
@@ -230,20 +230,20 @@ namespace UnitTests.AspNetCore.Attributes
|
||||
Assert.IsNull(context.Result);
|
||||
}
|
||||
|
||||
Assert.AreEqual(1, itemsCallback.Count);
|
||||
Assert.AreEqual(remote, itemsCallback["RemoteAddress"]);
|
||||
Assert.AreEqual(1, _itemsCallback.Count);
|
||||
Assert.AreEqual(remote, _itemsCallback["RemoteAddress"]);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ShouldAllowOnMissingConfiguration()
|
||||
{
|
||||
// arrange
|
||||
configKey = "Black:List";
|
||||
configExists = false;
|
||||
var attribute = new IPBlacklistAttribute
|
||||
_configKey = "Black:List";
|
||||
_configExists = false;
|
||||
var attribute = new IPBlockListAttribute
|
||||
{
|
||||
RestrictLocalAccess = true,
|
||||
ConfigurationKey = configKey
|
||||
BlockLocalAccess = true,
|
||||
ConfigurationKey = _configKey
|
||||
};
|
||||
var context = GetContext();
|
||||
|
||||
@@ -253,14 +253,14 @@ namespace UnitTests.AspNetCore.Attributes
|
||||
// assert
|
||||
Assert.IsNull(context.Result);
|
||||
|
||||
Assert.AreEqual(1, itemsCallback.Count);
|
||||
Assert.AreEqual(IPAddress.Loopback, itemsCallback["RemoteAddress"]);
|
||||
Assert.AreEqual(1, _itemsCallback.Count);
|
||||
Assert.AreEqual(IPAddress.Loopback, _itemsCallback["RemoteAddress"]);
|
||||
}
|
||||
|
||||
private ActionExecutingContext GetContext(IPAddress remote = null)
|
||||
{
|
||||
var requestHeaderMock = new Mock<IHeaderDictionary>();
|
||||
foreach (var header in requestHeaders)
|
||||
foreach (var header in _requestHeaders)
|
||||
{
|
||||
requestHeaderMock
|
||||
.Setup(h => h.ContainsKey(header.Key))
|
||||
@@ -286,11 +286,11 @@ namespace UnitTests.AspNetCore.Attributes
|
||||
var itemsMock = new Mock<IDictionary<object, object>>();
|
||||
itemsMock
|
||||
.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 children = new List<IConfigurationSection>();
|
||||
foreach (string ipAddress in restrictedIpsConfig)
|
||||
foreach (string ipAddress in _restrictedIpsConfig)
|
||||
{
|
||||
var csm = new Mock<IConfigurationSection>();
|
||||
csm.Setup(cs => cs.Value).Returns(ipAddress);
|
||||
@@ -304,8 +304,8 @@ namespace UnitTests.AspNetCore.Attributes
|
||||
.Returns(children);
|
||||
|
||||
configurationMock
|
||||
.Setup(c => c.GetSection(configKey))
|
||||
.Returns(configExists ? configSectionMock.Object : null);
|
||||
.Setup(c => c.GetSection(_configKey))
|
||||
.Returns(_configExists ? configSectionMock.Object : null);
|
||||
|
||||
var requestServicesMock = new Mock<IServiceProvider>();
|
||||
requestServicesMock
|
||||
@@ -12,28 +12,30 @@ namespace UnitTests.AspNetCore.Extensions
|
||||
[System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage]
|
||||
public class HttpContextExtensionsTests
|
||||
{
|
||||
private Mock<ISession> sessionMock;
|
||||
private Mock<ISession> _sessionMock;
|
||||
|
||||
private string tokenName;
|
||||
private string tokenValue;
|
||||
private string _tokenFormName;
|
||||
private string _tokenHeaderName;
|
||||
private string _tokenValue;
|
||||
|
||||
private Dictionary<string, string> requestHeaders;
|
||||
private Dictionary<string, string> requestQueries;
|
||||
private Dictionary<object, object> items;
|
||||
private Dictionary<string, string> _requestHeaders;
|
||||
private Dictionary<string, string> _requestQueries;
|
||||
private Dictionary<object, object> _items;
|
||||
|
||||
private IPAddress remote;
|
||||
private IPAddress _remote;
|
||||
|
||||
[TestInitialize]
|
||||
public void InitializeTests()
|
||||
{
|
||||
tokenName = null;
|
||||
tokenValue = null;
|
||||
_tokenFormName = null;
|
||||
_tokenHeaderName = null;
|
||||
_tokenValue = null;
|
||||
|
||||
requestHeaders = new Dictionary<string, string>();
|
||||
requestQueries = new Dictionary<string, string>();
|
||||
items = new Dictionary<object, object>();
|
||||
_requestHeaders = [];
|
||||
_requestQueries = [];
|
||||
_items = [];
|
||||
|
||||
remote = IPAddress.Loopback;
|
||||
_remote = IPAddress.Loopback;
|
||||
}
|
||||
|
||||
#region Antiforgery
|
||||
@@ -42,34 +44,38 @@ namespace UnitTests.AspNetCore.Extensions
|
||||
public void ShouldReturnAntiforgery()
|
||||
{
|
||||
// arrange
|
||||
tokenName = "af-token";
|
||||
tokenValue = "security_first";
|
||||
_tokenFormName = "af-token";
|
||||
_tokenHeaderName = "af-header";
|
||||
_tokenValue = "security_first";
|
||||
|
||||
var context = GetContext();
|
||||
|
||||
// act
|
||||
var result = context.GetAntiforgeryToken();
|
||||
var (formName, headerName, value) = context.GetAntiforgeryToken();
|
||||
|
||||
// assert
|
||||
Assert.AreEqual(tokenName, result.Name);
|
||||
Assert.AreEqual(tokenValue, result.Value);
|
||||
Assert.AreEqual(_tokenFormName, formName);
|
||||
Assert.AreEqual(_tokenHeaderName, headerName);
|
||||
Assert.AreEqual(_tokenValue, value);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ShouldReturnAntiforgeryNullService()
|
||||
{
|
||||
// arrange
|
||||
tokenName = "af-token";
|
||||
tokenValue = "security_first";
|
||||
_tokenFormName = "af-token";
|
||||
_tokenHeaderName = "af-header";
|
||||
_tokenValue = "security_first";
|
||||
|
||||
var context = GetContext(hasAntiforgery: false);
|
||||
|
||||
// act
|
||||
var result = context.GetAntiforgeryToken();
|
||||
var (formName, headerName, value) = context.GetAntiforgeryToken();
|
||||
|
||||
// assert
|
||||
Assert.AreEqual(null, result.Name);
|
||||
Assert.AreEqual(null, result.Value);
|
||||
Assert.IsNull(formName);
|
||||
Assert.IsNull(headerName);
|
||||
Assert.IsNull(value);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
@@ -79,11 +85,12 @@ namespace UnitTests.AspNetCore.Extensions
|
||||
var context = GetContext();
|
||||
|
||||
// act
|
||||
var result = context.GetAntiforgeryToken();
|
||||
var (formName, headerName, value) = context.GetAntiforgeryToken();
|
||||
|
||||
// assert
|
||||
Assert.AreEqual(null, result.Name);
|
||||
Assert.AreEqual(null, result.Value);
|
||||
Assert.IsNull(formName);
|
||||
Assert.IsNull(headerName);
|
||||
Assert.IsNull(value);
|
||||
}
|
||||
|
||||
#endregion Antiforgery
|
||||
@@ -94,7 +101,7 @@ namespace UnitTests.AspNetCore.Extensions
|
||||
public void ShouldReturnRemoteAddress()
|
||||
{
|
||||
// arrange
|
||||
remote = IPAddress.Parse("1.2.3.4");
|
||||
_remote = IPAddress.Parse("1.2.3.4");
|
||||
|
||||
var context = GetContext();
|
||||
|
||||
@@ -102,16 +109,19 @@ namespace UnitTests.AspNetCore.Extensions
|
||||
var result = context.GetRemoteIpAddress();
|
||||
|
||||
// assert
|
||||
Assert.AreEqual(remote, result);
|
||||
Assert.AreEqual(_remote, result);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ShouldReturnDefaultHeader()
|
||||
[DataTestMethod]
|
||||
[DataRow("Cf-Connecting-Ip")]
|
||||
[DataRow("X-Real-IP")]
|
||||
[DataRow("X-Forwarded-For")]
|
||||
public void ShouldReturnDefaultHeader(string headerName)
|
||||
{
|
||||
// arrange
|
||||
remote = IPAddress.Parse("1.2.3.4");
|
||||
_remote = IPAddress.Parse("1.2.3.4");
|
||||
var header = IPAddress.Parse("5.6.7.8");
|
||||
requestHeaders.Add("X-Forwarded-For", header.ToString());
|
||||
_requestHeaders.Add(headerName, header.ToString());
|
||||
|
||||
var context = GetContext();
|
||||
|
||||
@@ -119,7 +129,7 @@ namespace UnitTests.AspNetCore.Extensions
|
||||
var result = context.GetRemoteIpAddress();
|
||||
|
||||
// assert
|
||||
Assert.AreNotEqual(remote, result);
|
||||
Assert.AreNotEqual(_remote, result);
|
||||
Assert.AreEqual(header, result);
|
||||
}
|
||||
|
||||
@@ -127,18 +137,20 @@ namespace UnitTests.AspNetCore.Extensions
|
||||
public void ShouldReturnCustomHeader()
|
||||
{
|
||||
// arrange
|
||||
remote = IPAddress.Parse("1.2.3.4");
|
||||
_remote = IPAddress.Parse("1.2.3.4");
|
||||
string headerName = "FooBar";
|
||||
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();
|
||||
|
||||
// act
|
||||
var result = context.GetRemoteIpAddress(headerName: headerName);
|
||||
var result = context.GetRemoteIpAddress(ipHeaderName: headerName);
|
||||
|
||||
// assert
|
||||
Assert.AreNotEqual(remote, result);
|
||||
Assert.AreNotEqual(_remote, result);
|
||||
Assert.AreEqual(headerIp, result);
|
||||
}
|
||||
|
||||
@@ -146,8 +158,8 @@ namespace UnitTests.AspNetCore.Extensions
|
||||
public void ShouldReturnAddressInvalidHeader()
|
||||
{
|
||||
// arrange
|
||||
remote = IPAddress.Parse("1.2.3.4");
|
||||
requestHeaders.Add("X-Forwarded-For", "1.2.3:4");
|
||||
_remote = IPAddress.Parse("1.2.3.4");
|
||||
_requestHeaders.Add("X-Forwarded-For", "1.2.3:4");
|
||||
|
||||
var context = GetContext();
|
||||
|
||||
@@ -155,7 +167,44 @@ namespace UnitTests.AspNetCore.Extensions
|
||||
var result = context.GetRemoteIpAddress();
|
||||
|
||||
// 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
|
||||
@@ -166,7 +215,7 @@ namespace UnitTests.AspNetCore.Extensions
|
||||
public void ShouldReturnTrueOnLocal()
|
||||
{
|
||||
// arrange
|
||||
remote = IPAddress.Loopback;
|
||||
_remote = IPAddress.Loopback;
|
||||
|
||||
var context = GetContext();
|
||||
|
||||
@@ -181,7 +230,7 @@ namespace UnitTests.AspNetCore.Extensions
|
||||
public void ShouldReturnFalseOnRemote()
|
||||
{
|
||||
// arrange
|
||||
remote = IPAddress.Parse("1.2.3.4");
|
||||
_remote = IPAddress.Parse("1.2.3.4");
|
||||
|
||||
var context = GetContext();
|
||||
|
||||
@@ -196,9 +245,9 @@ namespace UnitTests.AspNetCore.Extensions
|
||||
public void ShouldReturnTrueOnDefaultHeader()
|
||||
{
|
||||
// arrange
|
||||
remote = IPAddress.Parse("1.2.3.4");
|
||||
_remote = IPAddress.Parse("1.2.3.4");
|
||||
var headerIp = IPAddress.Loopback;
|
||||
requestHeaders.Add("X-Forwarded-For", headerIp.ToString());
|
||||
_requestHeaders.Add("X-Forwarded-For", headerIp.ToString());
|
||||
|
||||
var context = GetContext();
|
||||
|
||||
@@ -213,15 +262,15 @@ namespace UnitTests.AspNetCore.Extensions
|
||||
public void ShouldReturnTrueOnCustomHeader()
|
||||
{
|
||||
// arrange
|
||||
remote = IPAddress.Parse("1.2.3.4");
|
||||
_remote = IPAddress.Parse("1.2.3.4");
|
||||
string headerName = "FooBar";
|
||||
var headerIp = IPAddress.Loopback;
|
||||
requestHeaders.Add(headerName, headerIp.ToString());
|
||||
_requestHeaders.Add(headerName, headerIp.ToString());
|
||||
|
||||
var context = GetContext();
|
||||
|
||||
// act
|
||||
bool result = context.IsLocalRequest(headerName: headerName);
|
||||
bool result = context.IsLocalRequest(ipHeaderName: headerName);
|
||||
|
||||
// assert
|
||||
Assert.IsTrue(result);
|
||||
@@ -232,7 +281,7 @@ namespace UnitTests.AspNetCore.Extensions
|
||||
{
|
||||
// arrange
|
||||
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();
|
||||
|
||||
@@ -249,12 +298,12 @@ namespace UnitTests.AspNetCore.Extensions
|
||||
// arrange
|
||||
string headerName = "FooBar";
|
||||
var headerIp = IPAddress.Parse("1.2.3.4");
|
||||
requestHeaders.Add(headerName, headerIp.ToString());
|
||||
_requestHeaders.Add(headerName, headerIp.ToString());
|
||||
|
||||
var context = GetContext();
|
||||
|
||||
// act
|
||||
bool result = context.IsLocalRequest(headerName: headerName);
|
||||
bool result = context.IsLocalRequest(ipHeaderName: headerName);
|
||||
|
||||
// assert
|
||||
Assert.IsFalse(result);
|
||||
@@ -284,8 +333,8 @@ namespace UnitTests.AspNetCore.Extensions
|
||||
string request = "abc";
|
||||
string query = "def";
|
||||
|
||||
items.Add("OriginalRequest", request);
|
||||
requestQueries.Add("ReturnUrl", query);
|
||||
_items.Add("OriginalRequest", request);
|
||||
_requestQueries.Add("ReturnUrl", query);
|
||||
|
||||
var context = GetContext();
|
||||
|
||||
@@ -302,7 +351,7 @@ namespace UnitTests.AspNetCore.Extensions
|
||||
{
|
||||
// arrange
|
||||
string query = "def";
|
||||
requestQueries.Add("ReturnUrl", query);
|
||||
_requestQueries.Add("ReturnUrl", query);
|
||||
|
||||
var context = GetContext();
|
||||
|
||||
@@ -327,7 +376,7 @@ namespace UnitTests.AspNetCore.Extensions
|
||||
context.ClearSession();
|
||||
|
||||
// assert
|
||||
sessionMock.Verify(s => s.Clear(), Times.Once);
|
||||
_sessionMock.Verify(s => s.Clear(), Times.Once);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
@@ -340,7 +389,7 @@ namespace UnitTests.AspNetCore.Extensions
|
||||
context.ClearSession();
|
||||
|
||||
// assert
|
||||
sessionMock.Verify(s => s.Clear(), Times.Never);
|
||||
_sessionMock.Verify(s => s.Clear(), Times.Never);
|
||||
}
|
||||
|
||||
#endregion Session
|
||||
@@ -349,7 +398,7 @@ namespace UnitTests.AspNetCore.Extensions
|
||||
{
|
||||
// Request
|
||||
var requestHeaderMock = new Mock<IHeaderDictionary>();
|
||||
foreach (var header in requestHeaders)
|
||||
foreach (var header in _requestHeaders)
|
||||
{
|
||||
requestHeaderMock
|
||||
.Setup(h => h.ContainsKey(header.Key))
|
||||
@@ -360,7 +409,7 @@ namespace UnitTests.AspNetCore.Extensions
|
||||
}
|
||||
|
||||
var requestQueryMock = new Mock<IQueryCollection>();
|
||||
foreach (var query in requestQueries)
|
||||
foreach (var query in _requestQueries)
|
||||
{
|
||||
requestQueryMock
|
||||
.Setup(h => h.ContainsKey(query.Key))
|
||||
@@ -385,7 +434,7 @@ namespace UnitTests.AspNetCore.Extensions
|
||||
var antiforgeryMock = new Mock<IAntiforgery>();
|
||||
antiforgeryMock
|
||||
.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
|
||||
.Setup(rs => rs.GetService(typeof(IAntiforgery)))
|
||||
@@ -399,10 +448,10 @@ namespace UnitTests.AspNetCore.Extensions
|
||||
.Returns(IPAddress.Loopback);
|
||||
connectionInfoMock
|
||||
.Setup(ci => ci.RemoteIpAddress)
|
||||
.Returns(remote);
|
||||
.Returns(_remote);
|
||||
|
||||
// Session
|
||||
sessionMock = new Mock<ISession>();
|
||||
_sessionMock = new Mock<ISession>();
|
||||
|
||||
var contextMock = new Mock<HttpContext>();
|
||||
contextMock
|
||||
@@ -416,12 +465,12 @@ namespace UnitTests.AspNetCore.Extensions
|
||||
.Returns(connectionInfoMock.Object);
|
||||
contextMock
|
||||
.Setup(c => c.Items)
|
||||
.Returns(items);
|
||||
.Returns(_items);
|
||||
if (hasSession)
|
||||
{
|
||||
contextMock
|
||||
.Setup(c => c.Session)
|
||||
.Returns(sessionMock.Object);
|
||||
.Returns(_sessionMock.Object);
|
||||
}
|
||||
|
||||
return contextMock.Object;
|
||||
|
||||
@@ -9,12 +9,12 @@ namespace UnitTests.AspNetCore.Extensions
|
||||
[System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage]
|
||||
public class ModelStateDictionaryExtensionsTests
|
||||
{
|
||||
private TestModel testModel;
|
||||
private TestModel _testModel;
|
||||
|
||||
[TestInitialize]
|
||||
public void InitializeTests()
|
||||
{
|
||||
testModel = new TestModel
|
||||
_testModel = new TestModel
|
||||
{
|
||||
ValueA = "A",
|
||||
ValueB = "B",
|
||||
@@ -33,7 +33,7 @@ namespace UnitTests.AspNetCore.Extensions
|
||||
var modelState = new ModelStateDictionary();
|
||||
|
||||
// act
|
||||
modelState.AddModelError(testModel, m => m.ValueA, "ShitHappens");
|
||||
modelState.AddModelError(_testModel, m => m.ValueA, "ShitHappens");
|
||||
|
||||
// assert
|
||||
Assert.AreEqual(1, modelState.Count);
|
||||
@@ -48,7 +48,7 @@ namespace UnitTests.AspNetCore.Extensions
|
||||
var modelState = new ModelStateDictionary();
|
||||
|
||||
// act
|
||||
modelState.AddModelError(testModel, m => m.SubModel.SubValueB, "ShitHappens");
|
||||
modelState.AddModelError(_testModel, m => m.SubModel.SubValueB, "ShitHappens");
|
||||
|
||||
// assert
|
||||
Assert.AreEqual(1, modelState.Count);
|
||||
@@ -64,7 +64,7 @@ namespace UnitTests.AspNetCore.Extensions
|
||||
ModelStateDictionary modelState = null;
|
||||
|
||||
// act
|
||||
modelState.AddModelError(testModel, m => m.SubModel.SubValueB, "ShitHappens");
|
||||
modelState.AddModelError(_testModel, m => m.SubModel.SubValueB, "ShitHappens");
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
@@ -75,7 +75,7 @@ namespace UnitTests.AspNetCore.Extensions
|
||||
var modelState = new ModelStateDictionary();
|
||||
|
||||
// act
|
||||
modelState.AddModelError(testModel, m => m, "ShitHappens");
|
||||
modelState.AddModelError(_testModel, m => m, "ShitHappens");
|
||||
}
|
||||
|
||||
internal class TestModel
|
||||
|
||||
@@ -10,12 +10,12 @@ namespace UnitTests.AspNetCore.Extensions
|
||||
[System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage]
|
||||
public class SessionExtensionsTests
|
||||
{
|
||||
private Mock<ISession> sessionMock;
|
||||
private Mock<ISession> _sessionMock;
|
||||
|
||||
private string sessionKey;
|
||||
private byte[] sessionValue;
|
||||
private string _sessionKey;
|
||||
private byte[] _sessionValue;
|
||||
|
||||
private TestModel model;
|
||||
private TestModel _model;
|
||||
|
||||
internal class TestModel
|
||||
{
|
||||
@@ -27,10 +27,10 @@ namespace UnitTests.AspNetCore.Extensions
|
||||
[TestInitialize]
|
||||
public void InitializeTests()
|
||||
{
|
||||
sessionKey = null;
|
||||
sessionValue = null;
|
||||
_sessionKey = null;
|
||||
_sessionValue = null;
|
||||
|
||||
model = new TestModel
|
||||
_model = new TestModel
|
||||
{
|
||||
ValueA = "A",
|
||||
ValueB = "B"
|
||||
@@ -41,7 +41,7 @@ namespace UnitTests.AspNetCore.Extensions
|
||||
public void ShouldCheckKeyExists()
|
||||
{
|
||||
// arrange
|
||||
sessionKey = "exists";
|
||||
_sessionKey = "exists";
|
||||
var session = GetSession();
|
||||
|
||||
// act
|
||||
@@ -57,25 +57,25 @@ namespace UnitTests.AspNetCore.Extensions
|
||||
public void ShouldGetValue()
|
||||
{
|
||||
// arrange
|
||||
sessionKey = "test";
|
||||
sessionValue = Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(model));
|
||||
_sessionKey = "test";
|
||||
_sessionValue = Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(_model));
|
||||
var session = GetSession();
|
||||
|
||||
// act
|
||||
var result = session.GetValue<TestModel>(sessionKey);
|
||||
var result = session.GetValue<TestModel>(_sessionKey);
|
||||
|
||||
// assert
|
||||
Assert.IsNotNull(result);
|
||||
Assert.AreEqual(model.ValueA, result.ValueA);
|
||||
Assert.AreEqual(model.ValueB, result.ValueB);
|
||||
Assert.AreEqual(_model.ValueA, result.ValueA);
|
||||
Assert.AreEqual(_model.ValueB, result.ValueB);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ShouldGetNull()
|
||||
{
|
||||
// arrange
|
||||
sessionKey = "foo";
|
||||
sessionValue = Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(model));
|
||||
_sessionKey = "foo";
|
||||
_sessionValue = Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(_model));
|
||||
var session = GetSession();
|
||||
|
||||
// act
|
||||
@@ -89,25 +89,25 @@ namespace UnitTests.AspNetCore.Extensions
|
||||
public void ShouldGetValueWithFallback()
|
||||
{
|
||||
// arrange
|
||||
sessionKey = "test";
|
||||
sessionValue = Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(model));
|
||||
_sessionKey = "test";
|
||||
_sessionValue = Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(_model));
|
||||
var session = GetSession();
|
||||
|
||||
// act
|
||||
var result = session.GetValue(sessionKey, new TestModel());
|
||||
var result = session.GetValue(_sessionKey, new TestModel());
|
||||
|
||||
// assert
|
||||
Assert.IsNotNull(result);
|
||||
Assert.AreEqual(model.ValueA, result.ValueA);
|
||||
Assert.AreEqual(model.ValueB, result.ValueB);
|
||||
Assert.AreEqual(_model.ValueA, result.ValueA);
|
||||
Assert.AreEqual(_model.ValueB, result.ValueB);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ShouldGetFallback()
|
||||
{
|
||||
// arrange
|
||||
sessionKey = "foo";
|
||||
sessionValue = Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(model));
|
||||
_sessionKey = "foo";
|
||||
_sessionValue = Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(_model));
|
||||
var session = GetSession();
|
||||
|
||||
// act
|
||||
@@ -127,33 +127,33 @@ namespace UnitTests.AspNetCore.Extensions
|
||||
var session = GetSession();
|
||||
|
||||
// act
|
||||
session.SetValue(key, model);
|
||||
session.SetValue(key, _model);
|
||||
|
||||
// arrange
|
||||
Assert.AreEqual(key, sessionKey);
|
||||
Assert.AreEqual(JsonConvert.SerializeObject(model), Encoding.UTF8.GetString(sessionValue));
|
||||
Assert.AreEqual(key, _sessionKey);
|
||||
Assert.AreEqual(JsonConvert.SerializeObject(_model), Encoding.UTF8.GetString(_sessionValue));
|
||||
}
|
||||
|
||||
private ISession GetSession()
|
||||
{
|
||||
string[] keys = new[] { sessionKey };
|
||||
string[] keys = [_sessionKey];
|
||||
|
||||
sessionMock = new Mock<ISession>();
|
||||
sessionMock
|
||||
.Setup(s => s.TryGetValue(It.IsAny<string>(), out sessionValue))
|
||||
.Returns<string, byte[]>((key, value) => sessionKey == key);
|
||||
sessionMock
|
||||
_sessionMock = new Mock<ISession>();
|
||||
_sessionMock
|
||||
.Setup(s => s.TryGetValue(It.IsAny<string>(), out _sessionValue))
|
||||
.Returns<string, byte[]>((key, value) => _sessionKey == key);
|
||||
_sessionMock
|
||||
.Setup(s => s.Set(It.IsAny<string>(), It.IsAny<byte[]>()))
|
||||
.Callback<string, byte[]>((key, value) =>
|
||||
{
|
||||
sessionKey = key;
|
||||
sessionValue = value;
|
||||
_sessionKey = key;
|
||||
_sessionValue = value;
|
||||
});
|
||||
sessionMock
|
||||
_sessionMock
|
||||
.Setup(s => s.Keys)
|
||||
.Returns(keys);
|
||||
|
||||
return sessionMock.Object;
|
||||
return _sessionMock.Object;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,38 +6,38 @@ using System.Security.Claims;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using AMWD.Common.AspNetCore.BasicAuthentication;
|
||||
using AMWD.Common.AspNetCore.Security.BasicAuthentication;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.Extensions.Primitives;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
using Moq;
|
||||
|
||||
namespace UnitTests.AspNetCore.BasicAuthentication
|
||||
namespace UnitTests.AspNetCore.Security.BasicAuthentication
|
||||
{
|
||||
[TestClass]
|
||||
[System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage]
|
||||
public class BasicAuthenticationMiddlewareTests
|
||||
{
|
||||
private Dictionary<string, string> requestHeaders;
|
||||
private Dictionary<string, string> _requestHeaders;
|
||||
|
||||
private Dictionary<string, string> responseHeadersCallback;
|
||||
private int responseStatusCodeCallback;
|
||||
private Dictionary<string, string> _responseHeadersCallback;
|
||||
private int _responseStatusCodeCallback;
|
||||
|
||||
private string validatorRealm;
|
||||
private ClaimsPrincipal validatorResponse;
|
||||
private List<(string username, string password, IPAddress ipAddr)> validatorCallback;
|
||||
private string _validatorRealm;
|
||||
private ClaimsPrincipal _validatorResponse;
|
||||
private List<(string username, string password, IPAddress ipAddr)> _validatorCallback;
|
||||
|
||||
[TestInitialize]
|
||||
public void InitializeTests()
|
||||
{
|
||||
requestHeaders = new Dictionary<string, string>();
|
||||
_requestHeaders = [];
|
||||
|
||||
responseHeadersCallback = new Dictionary<string, string>();
|
||||
responseStatusCodeCallback = 0;
|
||||
_responseHeadersCallback = [];
|
||||
_responseStatusCodeCallback = 0;
|
||||
|
||||
validatorRealm = null;
|
||||
validatorResponse = null;
|
||||
validatorCallback = new List<(string username, string password, IPAddress ipAddr)>();
|
||||
_validatorRealm = null;
|
||||
_validatorResponse = null;
|
||||
_validatorCallback = [];
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
@@ -47,8 +47,8 @@ namespace UnitTests.AspNetCore.BasicAuthentication
|
||||
string username = "user";
|
||||
string password = "pass:word";
|
||||
|
||||
requestHeaders.Add("Authorization", $"Basic {Convert.ToBase64String(Encoding.UTF8.GetBytes($"{username}:{password}"))}");
|
||||
validatorResponse = new ClaimsPrincipal();
|
||||
_requestHeaders.Add("Authorization", $"Basic {Convert.ToBase64String(Encoding.UTF8.GetBytes($"{username}:{password}"))}");
|
||||
_validatorResponse = new ClaimsPrincipal();
|
||||
|
||||
var middleware = GetMiddleware();
|
||||
var context = GetContext();
|
||||
@@ -57,13 +57,13 @@ namespace UnitTests.AspNetCore.BasicAuthentication
|
||||
await middleware.InvokeAsync(context);
|
||||
|
||||
// assert
|
||||
Assert.AreEqual(0, responseStatusCodeCallback); // not triggered
|
||||
Assert.AreEqual(0, responseHeadersCallback.Count);
|
||||
Assert.AreEqual(1, validatorCallback.Count);
|
||||
Assert.AreEqual(0, _responseStatusCodeCallback); // not triggered
|
||||
Assert.AreEqual(0, _responseHeadersCallback.Count);
|
||||
Assert.AreEqual(1, _validatorCallback.Count);
|
||||
|
||||
Assert.AreEqual(username, validatorCallback.First().username);
|
||||
Assert.AreEqual(password, validatorCallback.First().password);
|
||||
Assert.AreEqual(IPAddress.Loopback, validatorCallback.First().ipAddr);
|
||||
Assert.AreEqual(username, _validatorCallback.First().username);
|
||||
Assert.AreEqual(password, _validatorCallback.First().password);
|
||||
Assert.AreEqual(IPAddress.Loopback, _validatorCallback.First().ipAddr);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
@@ -77,13 +77,13 @@ namespace UnitTests.AspNetCore.BasicAuthentication
|
||||
await middleware.InvokeAsync(context);
|
||||
|
||||
// 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("WWW-Authenticate", responseHeadersCallback.Keys.First());
|
||||
Assert.AreEqual("Basic", responseHeadersCallback.Values.First());
|
||||
Assert.AreEqual(1, _responseHeadersCallback.Count);
|
||||
Assert.AreEqual("WWW-Authenticate", _responseHeadersCallback.Keys.First());
|
||||
Assert.AreEqual("Basic", _responseHeadersCallback.Values.First());
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
@@ -93,10 +93,10 @@ namespace UnitTests.AspNetCore.BasicAuthentication
|
||||
string username = "user";
|
||||
string password = "pw";
|
||||
|
||||
validatorRealm = "TEST";
|
||||
_validatorRealm = "TEST";
|
||||
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 context = GetContext(remote);
|
||||
@@ -105,16 +105,16 @@ namespace UnitTests.AspNetCore.BasicAuthentication
|
||||
await middleware.InvokeAsync(context);
|
||||
|
||||
// assert
|
||||
Assert.AreEqual(401, responseStatusCodeCallback);
|
||||
Assert.AreEqual(401, _responseStatusCodeCallback);
|
||||
|
||||
Assert.AreEqual(1, responseHeadersCallback.Count);
|
||||
Assert.AreEqual("WWW-Authenticate", responseHeadersCallback.Keys.First());
|
||||
Assert.AreEqual($"Basic realm=\"{validatorRealm}\"", responseHeadersCallback.Values.First());
|
||||
Assert.AreEqual(1, _responseHeadersCallback.Count);
|
||||
Assert.AreEqual("WWW-Authenticate", _responseHeadersCallback.Keys.First());
|
||||
Assert.AreEqual($"Basic realm=\"{_validatorRealm}\"", _responseHeadersCallback.Values.First());
|
||||
|
||||
Assert.AreEqual(1, validatorCallback.Count);
|
||||
Assert.AreEqual(username, validatorCallback.First().username);
|
||||
Assert.AreEqual(password, validatorCallback.First().password);
|
||||
Assert.AreEqual(remote, validatorCallback.First().ipAddr);
|
||||
Assert.AreEqual(1, _validatorCallback.Count);
|
||||
Assert.AreEqual(username, _validatorCallback.First().username);
|
||||
Assert.AreEqual(password, _validatorCallback.First().password);
|
||||
Assert.AreEqual(remote, _validatorCallback.First().ipAddr);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
@@ -123,7 +123,7 @@ namespace UnitTests.AspNetCore.BasicAuthentication
|
||||
// arrange
|
||||
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 context = GetContext();
|
||||
@@ -132,7 +132,7 @@ namespace UnitTests.AspNetCore.BasicAuthentication
|
||||
await middleware.InvokeAsync(context);
|
||||
|
||||
// assert
|
||||
Assert.AreEqual(500, responseStatusCodeCallback);
|
||||
Assert.AreEqual(500, _responseStatusCodeCallback);
|
||||
}
|
||||
|
||||
private BasicAuthenticationMiddleware GetMiddleware()
|
||||
@@ -141,11 +141,11 @@ namespace UnitTests.AspNetCore.BasicAuthentication
|
||||
var validatorMock = new Mock<IBasicAuthenticationValidator>();
|
||||
validatorMock
|
||||
.Setup(v => v.Realm)
|
||||
.Returns(validatorRealm);
|
||||
.Returns(_validatorRealm);
|
||||
validatorMock
|
||||
.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)))
|
||||
.ReturnsAsync(validatorResponse);
|
||||
.Callback<string, string, IPAddress, CancellationToken>((username, password, ipAddress, _) => _validatorCallback.Add((username, password, ipAddress)))
|
||||
.ReturnsAsync(_validatorResponse);
|
||||
|
||||
return new BasicAuthenticationMiddleware(nextMock.Object, validatorMock.Object);
|
||||
}
|
||||
@@ -154,7 +154,7 @@ namespace UnitTests.AspNetCore.BasicAuthentication
|
||||
{
|
||||
// Request
|
||||
var requestHeaderMock = new Mock<IHeaderDictionary>();
|
||||
foreach (var header in requestHeaders)
|
||||
foreach (var header in _requestHeaders)
|
||||
{
|
||||
requestHeaderMock
|
||||
.Setup(h => h.ContainsKey(header.Key))
|
||||
@@ -173,7 +173,7 @@ namespace UnitTests.AspNetCore.BasicAuthentication
|
||||
var responseHeaderMock = new Mock<IHeaderDictionary>();
|
||||
responseHeaderMock
|
||||
.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>();
|
||||
responseMock
|
||||
@@ -181,7 +181,7 @@ namespace UnitTests.AspNetCore.BasicAuthentication
|
||||
.Returns(responseHeaderMock.Object);
|
||||
responseMock
|
||||
.SetupSet(r => r.StatusCode = It.IsAny<int>())
|
||||
.Callback<int>((code) => responseStatusCodeCallback = code);
|
||||
.Callback<int>((code) => _responseStatusCodeCallback = code);
|
||||
|
||||
// Connection
|
||||
var connectionInfoMock = new Mock<ConnectionInfo>();
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,9 +1,4 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System.Reflection;
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
|
||||
|
||||
306
UnitTests/Common/Cli/CommandLineParserTests.cs
Normal file
306
UnitTests/Common/Cli/CommandLineParserTests.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
90
UnitTests/Common/Cli/EnumerableWalkerTests.cs
Normal file
90
UnitTests/Common/Cli/EnumerableWalkerTests.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -12,7 +12,7 @@ namespace UnitTests.Common.Extensions
|
||||
{
|
||||
// arrange
|
||||
string str = "Hello World!";
|
||||
byte[] bytes = new byte[] { 0xaf, 0xfe };
|
||||
byte[] bytes = [0xaf, 0xfe];
|
||||
|
||||
// act
|
||||
string strHash = str.Md5();
|
||||
@@ -28,7 +28,7 @@ namespace UnitTests.Common.Extensions
|
||||
{
|
||||
// arrange
|
||||
string str = "Hello World!";
|
||||
byte[] bytes = new byte[] { 0xaf, 0xfe };
|
||||
byte[] bytes = [0xaf, 0xfe];
|
||||
|
||||
// act
|
||||
string strHash = str.Sha1();
|
||||
@@ -44,7 +44,7 @@ namespace UnitTests.Common.Extensions
|
||||
{
|
||||
// arrange
|
||||
string str = "Hello World!";
|
||||
byte[] bytes = new byte[] { 0xaf, 0xfe };
|
||||
byte[] bytes = [0xaf, 0xfe];
|
||||
|
||||
// act
|
||||
string strHash = str.Sha256();
|
||||
@@ -60,7 +60,7 @@ namespace UnitTests.Common.Extensions
|
||||
{
|
||||
// arrange
|
||||
string str = "Hello World!";
|
||||
byte[] bytes = new byte[] { 0xaf, 0xfe };
|
||||
byte[] bytes = [0xaf, 0xfe];
|
||||
|
||||
// act
|
||||
string strHash = str.Sha512();
|
||||
|
||||
@@ -56,11 +56,11 @@ namespace UnitTests.Common.Extensions
|
||||
// arrange
|
||||
var innerExceptions = new List<Exception>
|
||||
{
|
||||
new Exception("Inner Exception 1."),
|
||||
new Exception("Inner Exception 2. See the inner exception for details.", new Exception("Inner Exception of Exception 2.")),
|
||||
new Exception("Inner Exception 3."),
|
||||
new Exception("Inner Exception 4."),
|
||||
new Exception("Inner Exception 5.")
|
||||
new("Inner Exception 1."),
|
||||
new("Inner Exception 2. See the inner exception for details.", new Exception("Inner Exception of Exception 2.")),
|
||||
new("Inner Exception 3."),
|
||||
new("Inner Exception 4."),
|
||||
new("Inner Exception 5.")
|
||||
};
|
||||
var aggregateException = new AggregateException("Lots of exceptions.", innerExceptions);
|
||||
string expectedMessage = "Inner Exception 1. Inner Exception 2. Inner Exception of Exception 2. Inner Exception 3.";
|
||||
|
||||
@@ -224,7 +224,7 @@ namespace UnitTests.Common.Extensions
|
||||
public void ShouldConvertToJArray()
|
||||
{
|
||||
// arrange
|
||||
string[] stringArray = new[] { "one", "two", "three" };
|
||||
string[] stringArray = ["one", "two", "three"];
|
||||
var objectArray = new[]
|
||||
{
|
||||
new JsonTestClass { StringValue = "One" },
|
||||
@@ -260,7 +260,7 @@ namespace UnitTests.Common.Extensions
|
||||
// act
|
||||
string topLevelString = jObj.GetValue<string>("stringValue");
|
||||
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 notExistingOnTopLevel = jObj.GetValue<string>("fancyValue");
|
||||
@@ -288,7 +288,7 @@ namespace UnitTests.Common.Extensions
|
||||
// act
|
||||
string topLevelString = jObj.GetValue("stringValue", "Test String");
|
||||
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 notExistingOnTopLevel = jObj.GetValue("fancyValue", "Party!");
|
||||
|
||||
@@ -154,7 +154,7 @@ namespace UnitTests.Common.Extensions
|
||||
catch (Exception)
|
||||
{ /* keep it quiet */ }
|
||||
});
|
||||
Task.WaitAll(awaitableTask);
|
||||
awaitableTask.Wait();
|
||||
|
||||
// assert
|
||||
Assert.IsTrue(isTimeout);
|
||||
@@ -183,7 +183,7 @@ namespace UnitTests.Common.Extensions
|
||||
catch (Exception)
|
||||
{ /* keep it quiet */ }
|
||||
});
|
||||
Task.WaitAll(awaitableTask);
|
||||
awaitableTask.Wait();
|
||||
|
||||
// assert
|
||||
Assert.IsTrue(isTimeout);
|
||||
@@ -212,7 +212,7 @@ namespace UnitTests.Common.Extensions
|
||||
catch (Exception)
|
||||
{ /* keep it quiet */ }
|
||||
});
|
||||
Task.WaitAll(awaitableTask);
|
||||
awaitableTask.Wait();
|
||||
|
||||
// assert
|
||||
Assert.IsTrue(isTimeout);
|
||||
|
||||
@@ -87,7 +87,7 @@ namespace UnitTests.Common.Extensions
|
||||
{
|
||||
// arrange
|
||||
byte[] bytes1 = null;
|
||||
byte[] bytes2 = Array.Empty<byte>();
|
||||
byte[] bytes2 = [];
|
||||
|
||||
// act
|
||||
string hex1 = bytes1.BytesToHex();
|
||||
@@ -102,7 +102,7 @@ namespace UnitTests.Common.Extensions
|
||||
public void ShouldReturnHexString()
|
||||
{
|
||||
// arrange
|
||||
byte[] bytes = new byte[] { 0xaf, 0xfe };
|
||||
byte[] bytes = [0xaf, 0xfe];
|
||||
|
||||
// act
|
||||
string hex = bytes.BytesToHex();
|
||||
@@ -116,7 +116,7 @@ namespace UnitTests.Common.Extensions
|
||||
public void ShouldReturnHexStringWithDelimiter()
|
||||
{
|
||||
// arrange
|
||||
byte[] bytes = new byte[] { 0xde, 0xad, 0xbe, 0xef };
|
||||
byte[] bytes = [0xde, 0xad, 0xbe, 0xef];
|
||||
|
||||
// act
|
||||
string hex = bytes.BytesToHex("_");
|
||||
|
||||
@@ -15,14 +15,14 @@ namespace UnitTests.Common.Logging
|
||||
[TestClass]
|
||||
public class FileLoggerTests
|
||||
{
|
||||
private Mock<StreamWriter> streamWriterMock;
|
||||
private Mock<StreamWriter> _streamWriterMock;
|
||||
|
||||
private List<string> lines;
|
||||
private List<string> _lines;
|
||||
|
||||
[TestInitialize]
|
||||
public void Initialize()
|
||||
{
|
||||
lines = new List<string>();
|
||||
_lines = [];
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
@@ -157,14 +157,14 @@ namespace UnitTests.Common.Logging
|
||||
|
||||
// act
|
||||
logger.Log(LogLevel.Information, "Test Message");
|
||||
SpinWait.SpinUntil(() => lines.Count == 1);
|
||||
SpinWait.SpinUntil(() => _lines.Count == 1);
|
||||
|
||||
// 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.FlushAsync(), Times.Once);
|
||||
streamWriterMock.VerifyNoOtherCalls();
|
||||
_streamWriterMock.Verify(sw => sw.WriteLineAsync(It.IsAny<string>()), Times.Once);
|
||||
_streamWriterMock.Verify(sw => sw.FlushAsync(), Times.Once);
|
||||
_streamWriterMock.VerifyNoOtherCalls();
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
@@ -177,18 +177,18 @@ namespace UnitTests.Common.Logging
|
||||
foreach (LogLevel level in Enum.GetValues<LogLevel>())
|
||||
logger.Log(level, "Test Message");
|
||||
|
||||
SpinWait.SpinUntil(() => lines.Count == 7);
|
||||
SpinWait.SpinUntil(() => _lines.Count == 7);
|
||||
|
||||
// assert
|
||||
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("INFO | 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("CRIT | Test Message"), Times.Once);
|
||||
streamWriterMock.Verify(sw => sw.WriteLineAsync(" | Test Message"), Times.Once);
|
||||
streamWriterMock.Verify(sw => sw.FlushAsync(), Times.AtLeastOnce);
|
||||
streamWriterMock.VerifyNoOtherCalls();
|
||||
_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("INFO | 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("CRIT | Test Message"), Times.Once);
|
||||
_streamWriterMock.Verify(sw => sw.WriteLineAsync(" | Test Message"), Times.Once);
|
||||
_streamWriterMock.Verify(sw => sw.FlushAsync(), Times.AtLeastOnce);
|
||||
_streamWriterMock.VerifyNoOtherCalls();
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
@@ -202,14 +202,14 @@ namespace UnitTests.Common.Logging
|
||||
foreach (LogLevel level in Enum.GetValues<LogLevel>())
|
||||
logger.Log(level, "Test Message");
|
||||
|
||||
SpinWait.SpinUntil(() => lines.Count == 3);
|
||||
SpinWait.SpinUntil(() => _lines.Count == 3);
|
||||
|
||||
// assert
|
||||
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(" | Test Message"), Times.Once);
|
||||
streamWriterMock.Verify(sw => sw.FlushAsync(), Times.AtLeastOnce);
|
||||
streamWriterMock.VerifyNoOtherCalls();
|
||||
_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(" | Test Message"), Times.Once);
|
||||
_streamWriterMock.Verify(sw => sw.FlushAsync(), Times.AtLeastOnce);
|
||||
_streamWriterMock.VerifyNoOtherCalls();
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
@@ -222,13 +222,13 @@ namespace UnitTests.Common.Logging
|
||||
|
||||
// act
|
||||
logger.LogWarning("Some Warning");
|
||||
SpinWait.SpinUntil(() => lines.Count == 1);
|
||||
SpinWait.SpinUntil(() => _lines.Count == 1);
|
||||
|
||||
// assert
|
||||
|
||||
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.VerifyNoOtherCalls();
|
||||
_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.VerifyNoOtherCalls();
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
@@ -241,12 +241,12 @@ namespace UnitTests.Common.Logging
|
||||
|
||||
// act
|
||||
logger.LogWarning("Some Warning");
|
||||
SpinWait.SpinUntil(() => lines.Count == 1);
|
||||
SpinWait.SpinUntil(() => _lines.Count == 1);
|
||||
|
||||
// assert
|
||||
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.VerifyNoOtherCalls();
|
||||
_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.VerifyNoOtherCalls();
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
@@ -264,12 +264,12 @@ namespace UnitTests.Common.Logging
|
||||
|
||||
// act
|
||||
logger.LogWarning("Some Warning");
|
||||
SpinWait.SpinUntil(() => lines.Count == 1);
|
||||
SpinWait.SpinUntil(() => _lines.Count == 1);
|
||||
|
||||
// assert
|
||||
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.VerifyNoOtherCalls();
|
||||
_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.VerifyNoOtherCalls();
|
||||
|
||||
Assert.AreEqual(0, new FileInfo(file).Length);
|
||||
}
|
||||
@@ -290,13 +290,13 @@ namespace UnitTests.Common.Logging
|
||||
using (var scope = logger.BeginScope("scope"))
|
||||
{
|
||||
logger.LogError("Test");
|
||||
SpinWait.SpinUntil(() => lines.Count == 1);
|
||||
SpinWait.SpinUntil(() => _lines.Count == 1);
|
||||
}
|
||||
|
||||
// assert
|
||||
streamWriterMock.Verify(sw => sw.WriteLineAsync("FAIL | [NamedInstance] Test"), Times.Once);
|
||||
streamWriterMock.Verify(sw => sw.FlushAsync(), Times.Once);
|
||||
streamWriterMock.VerifyNoOtherCalls();
|
||||
_streamWriterMock.Verify(sw => sw.WriteLineAsync("FAIL | [NamedInstance] Test"), Times.Once);
|
||||
_streamWriterMock.Verify(sw => sw.FlushAsync(), Times.Once);
|
||||
_streamWriterMock.VerifyNoOtherCalls();
|
||||
|
||||
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);
|
||||
@@ -311,12 +311,12 @@ namespace UnitTests.Common.Logging
|
||||
|
||||
// act
|
||||
logger.LogCritical(new Exception("TestException"), "");
|
||||
SpinWait.SpinUntil(() => lines.Count == 1);
|
||||
SpinWait.SpinUntil(() => _lines.Count == 1);
|
||||
|
||||
// assert
|
||||
streamWriterMock.Verify(sw => sw.WriteLineAsync("CRIT | System.Exception: TestException"), Times.Once);
|
||||
streamWriterMock.Verify(sw => sw.FlushAsync(), Times.Once);
|
||||
streamWriterMock.VerifyNoOtherCalls();
|
||||
_streamWriterMock.Verify(sw => sw.WriteLineAsync("CRIT | System.Exception: TestException"), Times.Once);
|
||||
_streamWriterMock.Verify(sw => sw.FlushAsync(), Times.Once);
|
||||
_streamWriterMock.VerifyNoOtherCalls();
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
@@ -327,12 +327,12 @@ namespace UnitTests.Common.Logging
|
||||
|
||||
// act
|
||||
logger.LogCritical(new Exception("TestException"), "Bad things happen...");
|
||||
SpinWait.SpinUntil(() => lines.Count == 1);
|
||||
SpinWait.SpinUntil(() => _lines.Count == 1);
|
||||
|
||||
// assert
|
||||
streamWriterMock.Verify(sw => sw.WriteLineAsync($"CRIT | Bad things happen...{Environment.NewLine} System.Exception: TestException"), Times.Once);
|
||||
streamWriterMock.Verify(sw => sw.FlushAsync(), Times.Once);
|
||||
streamWriterMock.VerifyNoOtherCalls();
|
||||
_streamWriterMock.Verify(sw => sw.WriteLineAsync($"CRIT | Bad things happen...{Environment.NewLine} System.Exception: TestException"), Times.Once);
|
||||
_streamWriterMock.Verify(sw => sw.FlushAsync(), Times.Once);
|
||||
_streamWriterMock.VerifyNoOtherCalls();
|
||||
}
|
||||
|
||||
private FileLogger GetFileLogger(string name = null, IExternalScopeProvider scopeProvider = null)
|
||||
@@ -340,10 +340,10 @@ namespace UnitTests.Common.Logging
|
||||
string tmpFilePath = Path.GetTempFileName();
|
||||
try
|
||||
{
|
||||
streamWriterMock = new Mock<StreamWriter>(Stream.Null);
|
||||
streamWriterMock
|
||||
_streamWriterMock = new Mock<StreamWriter>(Stream.Null);
|
||||
_streamWriterMock
|
||||
.Setup(sw => sw.WriteLineAsync(It.IsAny<string>()))
|
||||
.Callback<string>(line => lines.Add(line))
|
||||
.Callback<string>(line => _lines.Add(line))
|
||||
.Returns(Task.CompletedTask);
|
||||
|
||||
FileLogger fileLogger;
|
||||
@@ -356,9 +356,9 @@ namespace UnitTests.Common.Logging
|
||||
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.SetValue(fileLogger, streamWriterMock.Object);
|
||||
fieldInfo.SetValue(fileLogger, _streamWriterMock.Object);
|
||||
|
||||
return fileLogger;
|
||||
}
|
||||
|
||||
@@ -10,35 +10,35 @@ namespace UnitTests.Common.Utilities
|
||||
[System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage]
|
||||
public class AsyncQueueTests
|
||||
{
|
||||
private Queue<TestElement> internalQueue;
|
||||
private Queue<TestElement> _internalQueue;
|
||||
|
||||
private TestElement queueElement1;
|
||||
private TestElement queueElement2;
|
||||
private TestElement queueElement3;
|
||||
private TestElement _queueElement1;
|
||||
private TestElement _queueElement2;
|
||||
private TestElement _queueElement3;
|
||||
|
||||
[TestInitialize]
|
||||
public void InitializeTest()
|
||||
{
|
||||
queueElement1 = new TestElement
|
||||
_queueElement1 = new TestElement
|
||||
{
|
||||
Number = 111,
|
||||
Text = "one"
|
||||
};
|
||||
queueElement2 = new TestElement
|
||||
_queueElement2 = new TestElement
|
||||
{
|
||||
Number = 222,
|
||||
Text = "two"
|
||||
};
|
||||
queueElement3 = new TestElement
|
||||
_queueElement3 = new TestElement
|
||||
{
|
||||
Number = 333,
|
||||
Text = "three"
|
||||
};
|
||||
|
||||
internalQueue = new Queue<TestElement>();
|
||||
internalQueue.Enqueue(queueElement1);
|
||||
internalQueue.Enqueue(queueElement2);
|
||||
internalQueue.Enqueue(queueElement3);
|
||||
_internalQueue = new Queue<TestElement>();
|
||||
_internalQueue.Enqueue(_queueElement1);
|
||||
_internalQueue.Enqueue(_queueElement2);
|
||||
_internalQueue.Enqueue(_queueElement3);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
@@ -47,15 +47,15 @@ namespace UnitTests.Common.Utilities
|
||||
// arrange
|
||||
var element = new TestElement { Number = 1, Text = "Hello" };
|
||||
|
||||
internalQueue.Clear();
|
||||
_internalQueue.Clear();
|
||||
var queue = GetQueue();
|
||||
|
||||
// act
|
||||
queue.Enqueue(element);
|
||||
|
||||
// assert
|
||||
Assert.AreEqual(1, internalQueue.Count);
|
||||
Assert.AreEqual(internalQueue.Count, queue.Count);
|
||||
Assert.AreEqual(1, _internalQueue.Count);
|
||||
Assert.AreEqual(_internalQueue.Count, queue.Count);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
@@ -65,7 +65,7 @@ namespace UnitTests.Common.Utilities
|
||||
var element = new TestElement { Number = 1, Text = "Hello" };
|
||||
bool available = false;
|
||||
|
||||
internalQueue.Clear();
|
||||
_internalQueue.Clear();
|
||||
var queue = GetQueue();
|
||||
|
||||
// act
|
||||
@@ -88,7 +88,7 @@ namespace UnitTests.Common.Utilities
|
||||
var element = new TestElement { Number = 1, Text = "Hello" };
|
||||
TestElement callback = null;
|
||||
|
||||
internalQueue.Clear();
|
||||
_internalQueue.Clear();
|
||||
var queue = GetQueue();
|
||||
|
||||
// act
|
||||
@@ -110,18 +110,18 @@ namespace UnitTests.Common.Utilities
|
||||
// arrange
|
||||
var elements = new TestElement[]
|
||||
{
|
||||
new TestElement { Number = 1, Text = "Hello" },
|
||||
new TestElement { Number = 2, Text = "World" },
|
||||
new() { Number = 1, Text = "Hello" },
|
||||
new() { Number = 2, Text = "World" },
|
||||
};
|
||||
internalQueue.Clear();
|
||||
_internalQueue.Clear();
|
||||
var queue = GetQueue();
|
||||
|
||||
// act
|
||||
queue.Enqueue(elements);
|
||||
|
||||
// assert
|
||||
Assert.AreEqual(2, internalQueue.Count);
|
||||
Assert.AreEqual(queue.Count, internalQueue.Count);
|
||||
Assert.AreEqual(2, _internalQueue.Count);
|
||||
Assert.AreEqual(queue.Count, _internalQueue.Count);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
@@ -136,7 +136,7 @@ namespace UnitTests.Common.Utilities
|
||||
// assert
|
||||
Assert.IsTrue(isSuccess);
|
||||
Assert.IsNotNull(item);
|
||||
Assert.AreEqual(queueElement1, item);
|
||||
Assert.AreEqual(_queueElement1, item);
|
||||
Assert.AreEqual(3, queue.Count);
|
||||
}
|
||||
|
||||
@@ -144,7 +144,7 @@ namespace UnitTests.Common.Utilities
|
||||
public void ShouldNotPeekAValue()
|
||||
{
|
||||
// arrange
|
||||
internalQueue.Clear();
|
||||
_internalQueue.Clear();
|
||||
var queue = GetQueue();
|
||||
|
||||
// act
|
||||
@@ -167,7 +167,7 @@ namespace UnitTests.Common.Utilities
|
||||
// assert
|
||||
Assert.IsTrue(isSuccess);
|
||||
Assert.IsNotNull(item);
|
||||
Assert.AreEqual(queueElement1, item);
|
||||
Assert.AreEqual(_queueElement1, item);
|
||||
Assert.AreEqual(2, queue.Count);
|
||||
}
|
||||
|
||||
@@ -175,7 +175,7 @@ namespace UnitTests.Common.Utilities
|
||||
public void ShouldNotDequeueAValue()
|
||||
{
|
||||
// arrange
|
||||
internalQueue.Clear();
|
||||
_internalQueue.Clear();
|
||||
var queue = GetQueue();
|
||||
|
||||
// act
|
||||
@@ -193,14 +193,14 @@ namespace UnitTests.Common.Utilities
|
||||
var queue = GetQueue();
|
||||
|
||||
// act
|
||||
queue.Remove(queueElement2);
|
||||
queue.Remove(_queueElement2);
|
||||
var item1 = queue.Dequeue();
|
||||
var item2 = queue.Dequeue();
|
||||
|
||||
// assert
|
||||
Assert.AreEqual(0, queue.Count);
|
||||
Assert.AreEqual(queueElement1, item1);
|
||||
Assert.AreEqual(queueElement3, item2);
|
||||
Assert.AreEqual(_queueElement1, item1);
|
||||
Assert.AreEqual(_queueElement3, item2);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
@@ -217,22 +217,22 @@ namespace UnitTests.Common.Utilities
|
||||
|
||||
// assert
|
||||
Assert.AreEqual(0, queue.Count);
|
||||
Assert.AreEqual(queueElement1, item1);
|
||||
Assert.AreEqual(queueElement2, item2);
|
||||
Assert.AreEqual(queueElement3, item3);
|
||||
Assert.AreEqual(_queueElement1, item1);
|
||||
Assert.AreEqual(_queueElement2, item2);
|
||||
Assert.AreEqual(_queueElement3, item3);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public async Task ShouldAwaitOneDequeue()
|
||||
{
|
||||
// arrange
|
||||
internalQueue.Clear();
|
||||
_internalQueue.Clear();
|
||||
var queue = GetQueue();
|
||||
|
||||
var task = Task.Run(async () =>
|
||||
{
|
||||
await Task.Delay(1000);
|
||||
queue.Enqueue(new[] { queueElement1, queueElement2, queueElement3 });
|
||||
queue.Enqueue(new[] { _queueElement1, _queueElement2, _queueElement3 });
|
||||
});
|
||||
|
||||
// act
|
||||
@@ -241,20 +241,20 @@ namespace UnitTests.Common.Utilities
|
||||
// assert
|
||||
Assert.AreEqual(2, queue.Count);
|
||||
Assert.IsNotNull(item);
|
||||
Assert.AreEqual(queueElement1, item);
|
||||
Assert.AreEqual(_queueElement1, item);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public async Task ShouldAwaitManyDequeue()
|
||||
{
|
||||
// arrange
|
||||
internalQueue.Clear();
|
||||
_internalQueue.Clear();
|
||||
var queue = GetQueue();
|
||||
|
||||
var task = Task.Run(async () =>
|
||||
{
|
||||
await Task.Delay(1000);
|
||||
queue.Enqueue(new[] { queueElement1, queueElement2, queueElement3 });
|
||||
queue.Enqueue(new[] { _queueElement1, _queueElement2, _queueElement3 });
|
||||
});
|
||||
|
||||
// act
|
||||
@@ -264,21 +264,21 @@ namespace UnitTests.Common.Utilities
|
||||
Assert.AreEqual(1, queue.Count);
|
||||
Assert.IsNotNull(items);
|
||||
Assert.AreEqual(2, items.Length);
|
||||
Assert.AreEqual(queueElement1, items[0]);
|
||||
Assert.AreEqual(queueElement2, items[1]);
|
||||
Assert.AreEqual(_queueElement1, items[0]);
|
||||
Assert.AreEqual(_queueElement2, items[1]);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public async Task ShouldAwaitAllDequeue()
|
||||
{
|
||||
// arrange
|
||||
internalQueue.Clear();
|
||||
_internalQueue.Clear();
|
||||
var queue = GetQueue();
|
||||
|
||||
var task = Task.Run(async () =>
|
||||
{
|
||||
await Task.Delay(1000);
|
||||
queue.Enqueue(new[] { queueElement1, queueElement2, queueElement3 });
|
||||
queue.Enqueue(new[] { _queueElement1, _queueElement2, _queueElement3 });
|
||||
});
|
||||
|
||||
// act
|
||||
@@ -288,22 +288,22 @@ namespace UnitTests.Common.Utilities
|
||||
Assert.AreEqual(0, queue.Count);
|
||||
Assert.IsNotNull(items);
|
||||
Assert.AreEqual(3, items.Length);
|
||||
Assert.AreEqual(queueElement1, items[0]);
|
||||
Assert.AreEqual(queueElement2, items[1]);
|
||||
Assert.AreEqual(queueElement3, items[2]);
|
||||
Assert.AreEqual(_queueElement1, items[0]);
|
||||
Assert.AreEqual(_queueElement2, items[1]);
|
||||
Assert.AreEqual(_queueElement3, items[2]);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public async Task ShouldAwaitAvailableDequeue()
|
||||
{
|
||||
// arrange
|
||||
internalQueue.Clear();
|
||||
_internalQueue.Clear();
|
||||
var queue = GetQueue();
|
||||
|
||||
var task = Task.Run(async () =>
|
||||
{
|
||||
await Task.Delay(1000);
|
||||
queue.Enqueue(new[] { queueElement1, queueElement2, queueElement3 });
|
||||
queue.Enqueue(new[] { _queueElement1, _queueElement2, _queueElement3 });
|
||||
});
|
||||
|
||||
// act
|
||||
@@ -313,8 +313,8 @@ namespace UnitTests.Common.Utilities
|
||||
Assert.AreEqual(1, queue.Count);
|
||||
Assert.IsNotNull(items);
|
||||
Assert.AreEqual(2, items.Length);
|
||||
Assert.AreEqual(queueElement1, items[0]);
|
||||
Assert.AreEqual(queueElement2, items[1]);
|
||||
Assert.AreEqual(_queueElement1, items[0]);
|
||||
Assert.AreEqual(_queueElement2, items[1]);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
@@ -322,7 +322,7 @@ namespace UnitTests.Common.Utilities
|
||||
public async Task ShouldThrowArumentOutOfRangeException()
|
||||
{
|
||||
// arrange
|
||||
internalQueue.Clear();
|
||||
_internalQueue.Clear();
|
||||
var queue = GetQueue();
|
||||
|
||||
// act
|
||||
@@ -336,8 +336,8 @@ namespace UnitTests.Common.Utilities
|
||||
{
|
||||
var asyncQueue = new AsyncQueue<TestElement>();
|
||||
|
||||
var field = asyncQueue.GetType().GetField("queue", BindingFlags.Instance | BindingFlags.NonPublic);
|
||||
field.SetValue(asyncQueue, internalQueue);
|
||||
var field = asyncQueue.GetType().GetField("_queue", BindingFlags.Instance | BindingFlags.NonPublic);
|
||||
field.SetValue(asyncQueue, _internalQueue);
|
||||
|
||||
return asyncQueue;
|
||||
}
|
||||
|
||||
@@ -10,22 +10,22 @@ namespace UnitTests.Common.Utilities
|
||||
{
|
||||
[TestClass]
|
||||
[System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage]
|
||||
public class CryptographyHelperTests
|
||||
public partial class CryptographyHelperTests
|
||||
{
|
||||
private string keyFile;
|
||||
private CryptographyHelper cryptoHelper;
|
||||
private string _keyFile;
|
||||
private CryptographyHelper _cryptoHelper;
|
||||
|
||||
[TestInitialize]
|
||||
public void Initialize()
|
||||
{
|
||||
keyFile = Path.GetTempFileName();
|
||||
cryptoHelper = new CryptographyHelper(keyFile);
|
||||
_keyFile = Path.GetTempFileName();
|
||||
_cryptoHelper = new CryptographyHelper(_keyFile);
|
||||
}
|
||||
|
||||
[TestCleanup]
|
||||
public void Cleanup()
|
||||
{
|
||||
File.Delete(keyFile);
|
||||
File.Delete(_keyFile);
|
||||
}
|
||||
|
||||
#region Static
|
||||
@@ -40,12 +40,12 @@ namespace UnitTests.Common.Utilities
|
||||
// arrange
|
||||
using var _ = CryptographyHelperSaltMock.Create(0);
|
||||
|
||||
byte[] bytes = new byte[] { 0xaf, 0xfe };
|
||||
byte[] bytes = [0xaf, 0xfe];
|
||||
string str = "ABC";
|
||||
string password1 = "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
|
||||
byte[] cipherBytes1 = CryptographyHelper.AesEncrypt(bytes, password1);
|
||||
@@ -68,12 +68,12 @@ namespace UnitTests.Common.Utilities
|
||||
using var _ = CryptographyHelperSaltMock.Create(0);
|
||||
|
||||
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 password2 = "P@ssw0rd";
|
||||
|
||||
byte[] expectedBytes = new byte[] { 0xaf, 0xfe };
|
||||
byte[] expectedBytes = [0xaf, 0xfe];
|
||||
|
||||
// act
|
||||
byte[] plainBytes1 = CryptographyHelper.AesDecrypt(cipherBytes, password1);
|
||||
@@ -104,7 +104,7 @@ namespace UnitTests.Common.Utilities
|
||||
public void ShouldEncryptDecryptAesBytes()
|
||||
{
|
||||
// arrange
|
||||
byte[] plain = new byte[] { 0xaf, 0xfe };
|
||||
byte[] plain = [0xaf, 0xfe];
|
||||
string password = "P@ssw0rd!";
|
||||
|
||||
// act
|
||||
@@ -154,12 +154,12 @@ namespace UnitTests.Common.Utilities
|
||||
// arrange
|
||||
using var _ = CryptographyHelperSaltMock.Create(0);
|
||||
|
||||
byte[] bytes = new byte[] { 0xaf, 0xfe };
|
||||
byte[] bytes = [0xaf, 0xfe];
|
||||
string str = "ABC";
|
||||
string password1 = "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
|
||||
byte[] cipherBytes1 = CryptographyHelper.TripleDesEncrypt(bytes, password1);
|
||||
@@ -182,12 +182,12 @@ namespace UnitTests.Common.Utilities
|
||||
using var _ = CryptographyHelperSaltMock.Create(0);
|
||||
|
||||
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 password2 = "P@ssw0rd";
|
||||
|
||||
byte[] expectedBytes = new byte[] { 0xaf, 0xfe };
|
||||
byte[] expectedBytes = [0xaf, 0xfe];
|
||||
|
||||
// act
|
||||
byte[] plainBytes1 = CryptographyHelper.TripleDesDecrypt(cipherBytes, password1);
|
||||
@@ -218,7 +218,7 @@ namespace UnitTests.Common.Utilities
|
||||
public void ShouldEncryptDecryptTdesBytes()
|
||||
{
|
||||
// arrange
|
||||
byte[] plain = new byte[] { 0xaf, 0xfe };
|
||||
byte[] plain = [0xaf, 0xfe];
|
||||
string password = "P@ssw0rd!";
|
||||
|
||||
// act
|
||||
@@ -269,7 +269,7 @@ namespace UnitTests.Common.Utilities
|
||||
{
|
||||
// arrange
|
||||
string text = "Hello World!";
|
||||
byte[] bytes = new byte[] { 0xde, 0xad, 0xbe, 0xef };
|
||||
byte[] bytes = [0xde, 0xad, 0xbe, 0xef];
|
||||
string fileName = Path.GetTempFileName();
|
||||
|
||||
// act
|
||||
@@ -291,7 +291,7 @@ namespace UnitTests.Common.Utilities
|
||||
{
|
||||
// arrange
|
||||
string text = "Hello World!";
|
||||
byte[] bytes = new byte[] { 0xde, 0xad, 0xbe, 0xef };
|
||||
byte[] bytes = [0xde, 0xad, 0xbe, 0xef];
|
||||
string fileName = Path.GetTempFileName();
|
||||
|
||||
// act
|
||||
@@ -313,7 +313,7 @@ namespace UnitTests.Common.Utilities
|
||||
{
|
||||
// arrange
|
||||
string text = "Hello World!";
|
||||
byte[] bytes = new byte[] { 0xde, 0xad, 0xbe, 0xef };
|
||||
byte[] bytes = [0xde, 0xad, 0xbe, 0xef];
|
||||
string fileName = Path.GetTempFileName();
|
||||
|
||||
// act
|
||||
@@ -335,7 +335,7 @@ namespace UnitTests.Common.Utilities
|
||||
{
|
||||
// arrange
|
||||
string text = "Hello World!";
|
||||
byte[] bytes = new byte[] { 0xde, 0xad, 0xbe, 0xef };
|
||||
byte[] bytes = [0xde, 0xad, 0xbe, 0xef];
|
||||
string fileName = Path.GetTempFileName();
|
||||
|
||||
// act
|
||||
@@ -415,8 +415,8 @@ namespace UnitTests.Common.Utilities
|
||||
Assert.AreEqual(length, str1.Length);
|
||||
Assert.AreEqual(length, str2.Length);
|
||||
Assert.IsFalse(str1 == str2);
|
||||
Assert.IsFalse(Regex.IsMatch(str1, "[^0-9a-f]"));
|
||||
Assert.IsFalse(Regex.IsMatch(str2, "[^0-9a-f]"));
|
||||
Assert.IsFalse(RandomStringWithPoolRegex().IsMatch(str1));
|
||||
Assert.IsFalse(RandomStringWithPoolRegex().IsMatch(str2));
|
||||
}
|
||||
|
||||
#endregion Random
|
||||
@@ -537,7 +537,6 @@ namespace UnitTests.Common.Utilities
|
||||
Assert.IsTrue(!string.IsNullOrWhiteSpace(content));
|
||||
}
|
||||
|
||||
|
||||
[TestMethod]
|
||||
public void ShouldEncryptAesUsingKeyFile()
|
||||
{
|
||||
@@ -545,11 +544,11 @@ namespace UnitTests.Common.Utilities
|
||||
string str = "Hello World!";
|
||||
byte[] bytes = CryptographyHelper.GetRandomBytes(32);
|
||||
|
||||
string password = File.ReadAllText(keyFile);
|
||||
string password = File.ReadAllText(_keyFile);
|
||||
|
||||
// act
|
||||
string cipherStr = cryptoHelper.EncryptAes(str);
|
||||
byte[] cipherBytes = cryptoHelper.EncryptAes(bytes);
|
||||
string cipherStr = _cryptoHelper.EncryptAes(str);
|
||||
byte[] cipherBytes = _cryptoHelper.EncryptAes(bytes);
|
||||
|
||||
string plainStr = CryptographyHelper.AesDecrypt(cipherStr, password);
|
||||
byte[] plainBytes = CryptographyHelper.AesDecrypt(cipherBytes, password);
|
||||
@@ -568,14 +567,14 @@ namespace UnitTests.Common.Utilities
|
||||
string str = "Hello World!";
|
||||
byte[] bytes = CryptographyHelper.GetRandomBytes(32);
|
||||
|
||||
string password = File.ReadAllText(keyFile);
|
||||
string password = File.ReadAllText(_keyFile);
|
||||
|
||||
// act
|
||||
string cipherStr = CryptographyHelper.AesEncrypt(str, password);
|
||||
byte[] cipherBytes = CryptographyHelper.AesEncrypt(bytes, password);
|
||||
|
||||
string plainStr = cryptoHelper.DecryptAes(cipherStr);
|
||||
byte[] plainBytes = cryptoHelper.DecryptAes(cipherBytes);
|
||||
string plainStr = _cryptoHelper.DecryptAes(cipherStr);
|
||||
byte[] plainBytes = _cryptoHelper.DecryptAes(cipherBytes);
|
||||
|
||||
// assert
|
||||
Assert.AreNotEqual(str, cipherStr);
|
||||
@@ -591,11 +590,11 @@ namespace UnitTests.Common.Utilities
|
||||
string str = "Hello World!";
|
||||
byte[] bytes = CryptographyHelper.GetRandomBytes(32);
|
||||
|
||||
string password = File.ReadAllText(keyFile);
|
||||
string password = File.ReadAllText(_keyFile);
|
||||
|
||||
// act
|
||||
string cipherStr = cryptoHelper.EncryptTripleDes(str);
|
||||
byte[] cipherBytes = cryptoHelper.EncryptTripleDes(bytes);
|
||||
string cipherStr = _cryptoHelper.EncryptTripleDes(str);
|
||||
byte[] cipherBytes = _cryptoHelper.EncryptTripleDes(bytes);
|
||||
|
||||
string plainStr = CryptographyHelper.TripleDesDecrypt(cipherStr, password);
|
||||
byte[] plainBytes = CryptographyHelper.TripleDesDecrypt(cipherBytes, password);
|
||||
@@ -614,14 +613,14 @@ namespace UnitTests.Common.Utilities
|
||||
string str = "Hello World!";
|
||||
byte[] bytes = CryptographyHelper.GetRandomBytes(32);
|
||||
|
||||
string password = File.ReadAllText(keyFile);
|
||||
string password = File.ReadAllText(_keyFile);
|
||||
|
||||
// act
|
||||
string cipherStr = CryptographyHelper.TripleDesEncrypt(str, password);
|
||||
byte[] cipherBytes = CryptographyHelper.TripleDesEncrypt(bytes, password);
|
||||
|
||||
string plainStr = cryptoHelper.DecryptTripleDes(cipherStr);
|
||||
byte[] plainBytes = cryptoHelper.DecryptTripleDes(cipherBytes);
|
||||
string plainStr = _cryptoHelper.DecryptTripleDes(cipherStr);
|
||||
byte[] plainBytes = _cryptoHelper.DecryptTripleDes(cipherBytes);
|
||||
|
||||
// assert
|
||||
Assert.AreNotEqual(str, cipherStr);
|
||||
@@ -630,6 +629,9 @@ namespace UnitTests.Common.Utilities
|
||||
CollectionAssert.AreEqual(bytes, plainBytes);
|
||||
}
|
||||
|
||||
[GeneratedRegex("[^0-9a-f]")]
|
||||
private static partial Regex RandomStringWithPoolRegex();
|
||||
|
||||
#endregion Instance
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,11 +17,11 @@ namespace UnitTests.Common.Utilities
|
||||
// arrange
|
||||
int executionCount = 0;
|
||||
var delay = TimeSpan.FromMilliseconds(100);
|
||||
var action = () => { executionCount++; };
|
||||
void Action() { executionCount++; }
|
||||
|
||||
// act
|
||||
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);
|
||||
|
||||
// assert
|
||||
@@ -39,11 +39,11 @@ namespace UnitTests.Common.Utilities
|
||||
// arrange
|
||||
int executionCount = 0;
|
||||
var delay = TimeSpan.FromMilliseconds(100);
|
||||
var action = () => { executionCount++; };
|
||||
void Action() { executionCount++; }
|
||||
|
||||
// act
|
||||
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);
|
||||
|
||||
// assert
|
||||
@@ -61,11 +61,11 @@ namespace UnitTests.Common.Utilities
|
||||
// arrange
|
||||
int executionCount = 0;
|
||||
var delay = TimeSpan.FromMilliseconds(100);
|
||||
var action = () => { executionCount++; };
|
||||
void Action() { executionCount++; }
|
||||
|
||||
// act
|
||||
var cts = new CancellationTokenSource(delay.Add(TimeSpan.FromSeconds(1)));
|
||||
var delayedTask = DelayedTask.Create(action, delay);
|
||||
var delayedTask = DelayedTask.Create(Action, delay);
|
||||
delayedTask.Reset();
|
||||
SpinWait.SpinUntil(() => executionCount > 0 || cts.IsCancellationRequested);
|
||||
|
||||
@@ -84,11 +84,11 @@ namespace UnitTests.Common.Utilities
|
||||
// arrange
|
||||
int executionCount = 0;
|
||||
var delay = TimeSpan.FromMilliseconds(100);
|
||||
var action = () => { executionCount++; };
|
||||
void Action() { executionCount++; }
|
||||
|
||||
// act
|
||||
var cts = new CancellationTokenSource(delay.Add(TimeSpan.FromSeconds(1)));
|
||||
var delayedTask = DelayedTask.Run(action, delay);
|
||||
var delayedTask = DelayedTask.Run(Action, delay);
|
||||
delayedTask.Cancel();
|
||||
SpinWait.SpinUntil(() => executionCount > 0 || cts.IsCancellationRequested);
|
||||
|
||||
@@ -109,12 +109,12 @@ namespace UnitTests.Common.Utilities
|
||||
var sw = new Stopwatch();
|
||||
|
||||
var delay = TimeSpan.FromMilliseconds(200);
|
||||
var action = () => { sw.Stop(); executionCount++; };
|
||||
void Action() { sw.Stop(); executionCount++; }
|
||||
|
||||
// act
|
||||
var cts = new CancellationTokenSource(delay.Add(TimeSpan.FromSeconds(1)));
|
||||
sw.Start();
|
||||
var delayedTask = DelayedTask.Run(action, delay);
|
||||
var delayedTask = DelayedTask.Run(Action, delay);
|
||||
await Task.Delay(50);
|
||||
delayedTask.Reset();
|
||||
SpinWait.SpinUntil(() => executionCount > 0 || cts.IsCancellationRequested);
|
||||
@@ -138,12 +138,12 @@ namespace UnitTests.Common.Utilities
|
||||
var sw = new Stopwatch();
|
||||
|
||||
var delay = TimeSpan.FromMilliseconds(200);
|
||||
var action = () => { sw.Stop(); executionCount++; };
|
||||
void Action() { sw.Stop(); executionCount++; }
|
||||
|
||||
// act
|
||||
var cts = new CancellationTokenSource(delay.Add(TimeSpan.FromSeconds(1)));
|
||||
sw.Start();
|
||||
var delayedTask = DelayedTask.Create(action, delay);
|
||||
var delayedTask = DelayedTask.Create(Action, delay);
|
||||
await Task.Delay(50);
|
||||
bool isSuccess = delayedTask.ExecutePending();
|
||||
SpinWait.SpinUntil(() => executionCount > 0 || cts.IsCancellationRequested);
|
||||
@@ -169,12 +169,12 @@ namespace UnitTests.Common.Utilities
|
||||
var sw = new Stopwatch();
|
||||
|
||||
var delay = TimeSpan.FromMilliseconds(200);
|
||||
var action = () => { sw.Stop(); executionCount++; };
|
||||
void Action() { sw.Stop(); executionCount++; }
|
||||
|
||||
// act
|
||||
var cts = new CancellationTokenSource(delay.Add(TimeSpan.FromSeconds(1)));
|
||||
sw.Start();
|
||||
var delayedTask = DelayedTask.Run(action, delay);
|
||||
var delayedTask = DelayedTask.Run(Action, delay);
|
||||
await Task.Delay(50);
|
||||
bool isSuccess = delayedTask.ExecutePending();
|
||||
SpinWait.SpinUntil(() => executionCount > 0 || cts.IsCancellationRequested);
|
||||
@@ -197,8 +197,8 @@ namespace UnitTests.Common.Utilities
|
||||
// arrange
|
||||
int executionCount = 0;
|
||||
var delay = TimeSpan.FromMilliseconds(100);
|
||||
var action = () => { executionCount++; };
|
||||
var delayedTask = DelayedTask.Create(action, delay);
|
||||
void Action() { executionCount++; }
|
||||
var delayedTask = DelayedTask.Create(Action, delay);
|
||||
|
||||
// act
|
||||
delayedTask.Reset();
|
||||
@@ -219,10 +219,10 @@ namespace UnitTests.Common.Utilities
|
||||
{
|
||||
// arrange
|
||||
var delay = TimeSpan.FromMilliseconds(100);
|
||||
var action = () => { throw new Exception("TEST :D"); };
|
||||
static void Action() { throw new Exception("TEST :D"); }
|
||||
|
||||
// act
|
||||
var delayedTask = DelayedTask.Run(action, delay);
|
||||
var delayedTask = DelayedTask.Run(Action, delay);
|
||||
|
||||
var awaiter = delayedTask.GetAwaiter();
|
||||
SpinWait.SpinUntil(() => awaiter.IsCompleted);
|
||||
@@ -240,15 +240,12 @@ namespace UnitTests.Common.Utilities
|
||||
// arrange
|
||||
string exceptionText = null;
|
||||
var delay = TimeSpan.FromMilliseconds(100);
|
||||
var action = () => { throw new Exception("TEST :D"); };
|
||||
var exceptionHandler = (Exception ex) =>
|
||||
{
|
||||
exceptionText = ex.Message;
|
||||
};
|
||||
void Action() { throw new Exception("TEST :D"); }
|
||||
void ExceptionHandler(Exception ex) { exceptionText = ex.Message; }
|
||||
|
||||
// act
|
||||
var delayedTask = DelayedTask.Run(action, delay)
|
||||
.WithExceptionHandler(exceptionHandler);
|
||||
var delayedTask = DelayedTask.Run(Action, delay)
|
||||
.WithExceptionHandler(ExceptionHandler);
|
||||
|
||||
var awaiter = delayedTask.GetAwaiter();
|
||||
SpinWait.SpinUntil(() => awaiter.IsCompleted);
|
||||
@@ -267,8 +264,8 @@ namespace UnitTests.Common.Utilities
|
||||
// arrange
|
||||
int executionCount = 0;
|
||||
var delay = TimeSpan.FromMilliseconds(100);
|
||||
var action = () => { executionCount++; };
|
||||
var delayedTask = DelayedTask.Create(action, delay);
|
||||
void Action() { executionCount++; }
|
||||
var delayedTask = DelayedTask.Create(Action, delay);
|
||||
|
||||
// act
|
||||
delayedTask.Reset();
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using AMWD.Common.Utilities;
|
||||
@@ -17,11 +16,11 @@ namespace UnitTests.Common.Utilities
|
||||
// arrange
|
||||
int executionCount = 0;
|
||||
var delay = TimeSpan.FromMilliseconds(100);
|
||||
var function = () => { executionCount++; return new[] { 42, 21 }; };
|
||||
int[] Function() { executionCount++; return [42, 21]; }
|
||||
|
||||
// act
|
||||
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);
|
||||
|
||||
// assert
|
||||
@@ -34,15 +33,16 @@ namespace UnitTests.Common.Utilities
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[System.Diagnostics.CodeAnalysis.SuppressMessage("Performance", "CA1861")]
|
||||
public async Task ShouldCreateNewDelayedTaskStarting()
|
||||
{
|
||||
// arrange
|
||||
int executionCount = 0;
|
||||
var delay = TimeSpan.FromMilliseconds(100);
|
||||
var function = () => { executionCount++; return new[] { 42, 21 }; };
|
||||
int[] Function() { executionCount++; return [42, 21]; }
|
||||
|
||||
// act
|
||||
var delayedTaskWithResult = DelayedTask.Run(function, delay);
|
||||
var delayedTaskWithResult = DelayedTask.Run(Function, delay);
|
||||
int[] result = await delayedTaskWithResult;
|
||||
|
||||
// assert
|
||||
@@ -61,11 +61,11 @@ namespace UnitTests.Common.Utilities
|
||||
// arrange
|
||||
var delay = TimeSpan.FromMilliseconds(100);
|
||||
#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
|
||||
|
||||
// act
|
||||
var delayedTaskWithResult = DelayedTask.Run(function, delay);
|
||||
var delayedTaskWithResult = DelayedTask.Run(Function, delay);
|
||||
|
||||
var awaiter = delayedTaskWithResult.GetAwaiter();
|
||||
SpinWait.SpinUntil(() => awaiter.IsCompleted);
|
||||
@@ -84,16 +84,13 @@ namespace UnitTests.Common.Utilities
|
||||
string exceptionText = null;
|
||||
var delay = TimeSpan.FromMilliseconds(100);
|
||||
#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
|
||||
var exceptionHandler = (Exception ex) =>
|
||||
{
|
||||
exceptionText = ex.Message;
|
||||
};
|
||||
void ExceptionHandler(Exception ex) { exceptionText = ex.Message; }
|
||||
|
||||
// act
|
||||
var delayedTaskWithResult = DelayedTask.Run(function, delay)
|
||||
.WithExceptionHandler(exceptionHandler);
|
||||
var delayedTaskWithResult = DelayedTask.Run(Function, delay)
|
||||
.WithExceptionHandler(ExceptionHandler);
|
||||
|
||||
var awaiter = delayedTaskWithResult.GetAwaiter();
|
||||
SpinWait.SpinUntil(() => awaiter.IsCompleted);
|
||||
@@ -107,13 +104,14 @@ namespace UnitTests.Common.Utilities
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[System.Diagnostics.CodeAnalysis.SuppressMessage("Performance", "CA1861")]
|
||||
public async Task ShouldReturnNormalTask()
|
||||
{
|
||||
// arrange
|
||||
int executionCount = 0;
|
||||
var delay = TimeSpan.FromMilliseconds(100);
|
||||
var function = () => { executionCount++; return new[] { 42, 21 }; };
|
||||
var delayedTaskWithResult = DelayedTask.Create(function, delay);
|
||||
int[] Function() { executionCount++; return [42, 21]; }
|
||||
var delayedTaskWithResult = DelayedTask.Create(Function, delay);
|
||||
|
||||
// act
|
||||
delayedTaskWithResult.Reset();
|
||||
|
||||
@@ -7,11 +7,11 @@ namespace UnitTests.Common.Utils
|
||||
[System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage]
|
||||
internal class CryptographyHelperSaltMock : IDisposable
|
||||
{
|
||||
private readonly int saltLength;
|
||||
private readonly int _saltLength;
|
||||
|
||||
private CryptographyHelperSaltMock(int saltLength)
|
||||
{
|
||||
this.saltLength = typeof(CryptographyHelper).AsDynamicType().saltLength;
|
||||
_saltLength = typeof(CryptographyHelper).AsDynamicType()._saltLength;
|
||||
SetSaltLength(saltLength);
|
||||
}
|
||||
|
||||
@@ -19,9 +19,9 @@ namespace UnitTests.Common.Utils
|
||||
=> new CryptographyHelperSaltMock(saltLength);
|
||||
|
||||
public void Dispose()
|
||||
=> SetSaltLength(saltLength);
|
||||
=> SetSaltLength(_saltLength);
|
||||
|
||||
private static void SetSaltLength(int length)
|
||||
=> typeof(CryptographyHelper).AsDynamicType().saltLength = length;
|
||||
=> typeof(CryptographyHelper).AsDynamicType()._saltLength = length;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,13 +4,8 @@ namespace UnitTests.Common.Utils
|
||||
{
|
||||
[System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage]
|
||||
[AttributeUsage(AttributeTargets.All, AllowMultiple = true)]
|
||||
internal class CustomMultipleAttribute : Attribute
|
||||
internal class CustomMultipleAttribute(string name) : Attribute
|
||||
{
|
||||
public CustomMultipleAttribute(string name)
|
||||
{
|
||||
Name = name;
|
||||
}
|
||||
|
||||
public string Name { get; set; }
|
||||
public string Name { get; set; } = name;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -33,18 +33,21 @@ namespace UnitTests.Common.Utils
|
||||
[System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage]
|
||||
internal class JsonErrorClass
|
||||
{
|
||||
private int? number;
|
||||
private int? _number;
|
||||
|
||||
public int Number
|
||||
{
|
||||
get
|
||||
{
|
||||
if (number.HasValue)
|
||||
return number.Value;
|
||||
if (_number.HasValue)
|
||||
return _number.Value;
|
||||
|
||||
throw new Exception("Null value");
|
||||
}
|
||||
set { number = value; }
|
||||
set
|
||||
{
|
||||
_number = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,11 +6,11 @@ namespace UnitTests.Common.Utils
|
||||
[System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage]
|
||||
internal class TimeZoneInfoLocalMock : IDisposable
|
||||
{
|
||||
private readonly TimeZoneInfo localTimeZoneInfo;
|
||||
private readonly TimeZoneInfo _localTimeZoneInfo;
|
||||
|
||||
private TimeZoneInfoLocalMock(TimeZoneInfo timeZoneInfo)
|
||||
{
|
||||
localTimeZoneInfo = TimeZoneInfo.Local;
|
||||
_localTimeZoneInfo = TimeZoneInfo.Local;
|
||||
SetLocalTimeZone(timeZoneInfo);
|
||||
}
|
||||
|
||||
@@ -18,7 +18,7 @@ namespace UnitTests.Common.Utils
|
||||
=> new TimeZoneInfoLocalMock(mockTimeZoneInfo);
|
||||
|
||||
public void Dispose()
|
||||
=> SetLocalTimeZone(localTimeZoneInfo);
|
||||
=> SetLocalTimeZone(_localTimeZoneInfo);
|
||||
|
||||
private static void SetLocalTimeZone(TimeZoneInfo timeZoneInfo)
|
||||
=> typeof(TimeZoneInfo).AsDynamicType().s_cachedData._localTimeZone = timeZoneInfo;
|
||||
|
||||
@@ -1,24 +1,23 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net6.0</TargetFramework>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<IsPackable>false</IsPackable>
|
||||
<CollectCoverage>true</CollectCoverage>
|
||||
<GenerateDocumentationFile>false</GenerateDocumentationFile>
|
||||
<Configurations>Debug;Release;DebugLocal</Configurations>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="coverlet.msbuild" Version="3.2.0">
|
||||
<PackageReference Include="coverlet.msbuild" Version="6.0.0">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="DNS" Version="7.0.0" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.5.0" />
|
||||
<PackageReference Include="Moq" Version="4.18.4" />
|
||||
<PackageReference Include="MSTest.TestAdapter" Version="3.0.2" />
|
||||
<PackageReference Include="MSTest.TestFramework" Version="3.0.2" />
|
||||
<PackageReference Include="ReflectionMagic" Version="4.1.0" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.7.2" />
|
||||
<PackageReference Include="Moq" Version="4.20.69" />
|
||||
<PackageReference Include="MSTest.TestAdapter" Version="3.1.1" />
|
||||
<PackageReference Include="MSTest.TestFramework" Version="3.1.1" />
|
||||
<PackageReference Include="ReflectionMagic" Version="5.0.0" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
8
nuget.config
Normal file
8
nuget.config
Normal 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>
|
||||
Reference in New Issue
Block a user