mirror of
https://github.com/AlexMacocian/SystemExtensions.git
synced 2026-07-21 00:29:29 +00:00
Compare commits
4 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 721b444dda | |||
| 1c7d3a2be2 | |||
| 0a30872781 | |||
| 7eed1ca466 |
@@ -21,6 +21,7 @@ jobs:
|
|||||||
Test_Project_Path: SystemExtensions.Tests\SystemExtensions.NetStandard.Tests.csproj
|
Test_Project_Path: SystemExtensions.Tests\SystemExtensions.NetStandard.Tests.csproj
|
||||||
DI_Test_Project_Path: SystemExtensions.DependencyInjection.Tests\SystemExtensions.NetStandard.DependencyInjection.Tests.csproj
|
DI_Test_Project_Path: SystemExtensions.DependencyInjection.Tests\SystemExtensions.NetStandard.DependencyInjection.Tests.csproj
|
||||||
Source_Project_Path: SystemExtensions.NetStandard\SystemExtensions.NetStandard.csproj
|
Source_Project_Path: SystemExtensions.NetStandard\SystemExtensions.NetStandard.csproj
|
||||||
|
Core_Project_Path: SystemExtensions.NetCore\SystemExtensions.NetCore.csproj
|
||||||
Actions_Allow_Unsecure_Commands: true
|
Actions_Allow_Unsecure_Commands: true
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
@@ -32,7 +33,7 @@ jobs:
|
|||||||
- name: Install .NET Core
|
- name: Install .NET Core
|
||||||
uses: actions/setup-dotnet@v1
|
uses: actions/setup-dotnet@v1
|
||||||
with:
|
with:
|
||||||
dotnet-version: '5.0.202'
|
dotnet-version: '6.0.x'
|
||||||
|
|
||||||
- name: Setup MSBuild.exe
|
- name: Setup MSBuild.exe
|
||||||
uses: microsoft/setup-msbuild@v1.0.1
|
uses: microsoft/setup-msbuild@v1.0.1
|
||||||
@@ -45,6 +46,9 @@ jobs:
|
|||||||
- name: Build SystemExtensions.NetStandard project
|
- name: Build SystemExtensions.NetStandard project
|
||||||
run: dotnet build SystemExtensions.NetStandard -c $env:Configuration
|
run: dotnet build SystemExtensions.NetStandard -c $env:Configuration
|
||||||
|
|
||||||
|
- name: Build SystemExtensions.NetCore project
|
||||||
|
run: dotnet build SystemExtensions.NetCore -c $env:Configuration
|
||||||
|
|
||||||
- name: Build SystemExtensions.NetStandard.DependencyInjection project
|
- name: Build SystemExtensions.NetStandard.DependencyInjection project
|
||||||
run: dotnet build SystemExtensions.NetStandard.DependencyInjection -c $env:Configuration
|
run: dotnet build SystemExtensions.NetStandard.DependencyInjection -c $env:Configuration
|
||||||
|
|
||||||
@@ -57,6 +61,12 @@ jobs:
|
|||||||
PROJECT_FILE_PATH: SystemExtensions.NetStandard\SystemExtensions.NetStandard.csproj
|
PROJECT_FILE_PATH: SystemExtensions.NetStandard\SystemExtensions.NetStandard.csproj
|
||||||
NUGET_KEY: ${{secrets.NUGET_API_KEY}}
|
NUGET_KEY: ${{secrets.NUGET_API_KEY}}
|
||||||
|
|
||||||
|
- name: Push SystemExtensions.NetCore nuget package
|
||||||
|
uses: brandedoutcast/publish-nuget@v2.5.5
|
||||||
|
with:
|
||||||
|
PROJECT_FILE_PATH: SystemExtensions.NetCore\SystemExtensions.NetCore.csproj
|
||||||
|
NUGET_KEY: ${{secrets.NUGET_API_KEY}}
|
||||||
|
|
||||||
- name: Push SystemExtensions.NetStandard.DependencyInjection nuget package
|
- name: Push SystemExtensions.NetStandard.DependencyInjection nuget package
|
||||||
uses: brandedoutcast/publish-nuget@v2.5.5
|
uses: brandedoutcast/publish-nuget@v2.5.5
|
||||||
with:
|
with:
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ jobs:
|
|||||||
- name: Install .NET Core
|
- name: Install .NET Core
|
||||||
uses: actions/setup-dotnet@v1
|
uses: actions/setup-dotnet@v1
|
||||||
with:
|
with:
|
||||||
dotnet-version: '5.0.202'
|
dotnet-version: '6.0.x'
|
||||||
|
|
||||||
- name: Setup MSBuild.exe
|
- name: Setup MSBuild.exe
|
||||||
uses: microsoft/setup-msbuild@v1.0.1
|
uses: microsoft/setup-msbuild@v1.0.1
|
||||||
|
|||||||
+1
-1
@@ -1,7 +1,7 @@
|
|||||||
<Project Sdk="Microsoft.NET.Sdk">
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFramework>net5.0</TargetFramework>
|
<TargetFramework>net6.0</TargetFramework>
|
||||||
|
|
||||||
<IsPackable>false</IsPackable>
|
<IsPackable>false</IsPackable>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|||||||
@@ -0,0 +1,22 @@
|
|||||||
|
using System.Runtime.CompilerServices;
|
||||||
|
|
||||||
|
namespace System.Core.Extensions;
|
||||||
|
|
||||||
|
public static class ObjectExtensions
|
||||||
|
{
|
||||||
|
public static T ThrowIfNull<T>([ValidatedNotNull] this T obj, [CallerArgumentExpression("obj")] string? paramName = null)
|
||||||
|
where T : class
|
||||||
|
{
|
||||||
|
if (obj is null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(paramName);
|
||||||
|
}
|
||||||
|
|
||||||
|
return obj;
|
||||||
|
}
|
||||||
|
|
||||||
|
[AttributeUsage(AttributeTargets.Parameter)]
|
||||||
|
sealed class ValidatedNotNullAttribute : Attribute
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<TargetFramework>net6.0</TargetFramework>
|
||||||
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
|
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
|
||||||
|
<Nullable>enable</Nullable>
|
||||||
|
<RootNamespace>System</RootNamespace>
|
||||||
|
<PackageLicenseFile>LICENSE</PackageLicenseFile>
|
||||||
|
<PackageRequireLicenseAcceptance>true</PackageRequireLicenseAcceptance>
|
||||||
|
<Version>1.0.1</Version>
|
||||||
|
<Authors>Alexandru Macocian</Authors>
|
||||||
|
<RepositoryUrl>https://github.com/AlexMacocian/SystemExtensions</RepositoryUrl>
|
||||||
|
<Description>Extensions for the System namespace</Description>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<None Include="../LICENSE">
|
||||||
|
<Pack>True</Pack>
|
||||||
|
<PackagePath></PackagePath>
|
||||||
|
</None>
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
</Project>
|
||||||
+2
-1
@@ -6,9 +6,10 @@
|
|||||||
<PackageLicenseFile>LICENSE</PackageLicenseFile>
|
<PackageLicenseFile>LICENSE</PackageLicenseFile>
|
||||||
<PackageRequireLicenseAcceptance>true</PackageRequireLicenseAcceptance>
|
<PackageRequireLicenseAcceptance>true</PackageRequireLicenseAcceptance>
|
||||||
<LangVersion>latest</LangVersion>
|
<LangVersion>latest</LangVersion>
|
||||||
<Version>1.1.6</Version>
|
<Version>1.1.7</Version>
|
||||||
<Authors>Alexandru Macocian</Authors>
|
<Authors>Alexandru Macocian</Authors>
|
||||||
<RepositoryUrl>https://github.com/AlexMacocian/SystemExtensions</RepositoryUrl>
|
<RepositoryUrl>https://github.com/AlexMacocian/SystemExtensions</RepositoryUrl>
|
||||||
|
<Description>Extensions for the Slim Dependency Injection engine</Description>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
using FluentAssertions;
|
using FluentAssertions;
|
||||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Rng;
|
using System.Security.Rng;
|
||||||
|
|
||||||
namespace SystemExtensions.NetStandard.Security.Tests
|
namespace SystemExtensions.NetStandard.Security.Tests
|
||||||
{
|
{
|
||||||
|
|||||||
+1
-1
@@ -1,7 +1,7 @@
|
|||||||
using FluentAssertions;
|
using FluentAssertions;
|
||||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||||
using System;
|
using System;
|
||||||
using System.Hashing;
|
using System.Security.Hashing;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
namespace SystemExtensions.NetStandard.Security.Tests
|
namespace SystemExtensions.NetStandard.Security.Tests
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
using FluentAssertions;
|
using FluentAssertions;
|
||||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||||
using System;
|
using System.Security.Encryption;
|
||||||
using System.Encryption;
|
|
||||||
|
|
||||||
namespace SystemExtensions.NetStandard.Security.Tests
|
namespace SystemExtensions.NetStandard.Security.Tests
|
||||||
{
|
{
|
||||||
|
|||||||
+1
-1
@@ -1,7 +1,7 @@
|
|||||||
<Project Sdk="Microsoft.NET.Sdk">
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFramework>net5.0</TargetFramework>
|
<TargetFramework>net6.0</TargetFramework>
|
||||||
|
|
||||||
<IsPackable>false</IsPackable>
|
<IsPackable>false</IsPackable>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
using System.Security.Cryptography;
|
using System.Security.Cryptography;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
|
|
||||||
namespace System.Encryption
|
namespace System.Security.Encryption
|
||||||
{
|
{
|
||||||
public sealed class SecureString
|
public sealed class SecureString
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
namespace System.Hashing
|
namespace System.Security.Hashing
|
||||||
{
|
{
|
||||||
public interface IPasswordHashingService
|
public interface IPasswordHashingService
|
||||||
{
|
{
|
||||||
|
|||||||
+1
-1
@@ -1,7 +1,7 @@
|
|||||||
using System.Security.Cryptography;
|
using System.Security.Cryptography;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
namespace System.Hashing
|
namespace System.Security.Hashing
|
||||||
{
|
{
|
||||||
public sealed class Rfc2898DeriveBytesPasswordHashingService : IPasswordHashingService
|
public sealed class Rfc2898DeriveBytesPasswordHashingService : IPasswordHashingService
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
using System.Security.Cryptography;
|
using System.Security.Cryptography;
|
||||||
|
|
||||||
namespace System.Rng
|
namespace System.Security.Rng
|
||||||
{
|
{
|
||||||
public sealed class CryptoRngProvider : ICryptoRngProvider, IDisposable
|
public sealed class CryptoRngProvider : ICryptoRngProvider, IDisposable
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
namespace System.Rng
|
namespace System.Security.Rng
|
||||||
{
|
{
|
||||||
public interface ICryptoRngProvider
|
public interface ICryptoRngProvider
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -7,9 +7,10 @@
|
|||||||
<PackageLicenseFile>LICENSE</PackageLicenseFile>
|
<PackageLicenseFile>LICENSE</PackageLicenseFile>
|
||||||
<PackageRequireLicenseAcceptance>true</PackageRequireLicenseAcceptance>
|
<PackageRequireLicenseAcceptance>true</PackageRequireLicenseAcceptance>
|
||||||
<RootNamespace>System</RootNamespace>
|
<RootNamespace>System</RootNamespace>
|
||||||
<Version>1.2.1</Version>
|
<Version>1.2.3</Version>
|
||||||
<Authors>Alexandru Macocian</Authors>
|
<Authors>Alexandru Macocian</Authors>
|
||||||
<RepositoryUrl>https://github.com/AlexMacocian/SystemExtensions</RepositoryUrl>
|
<RepositoryUrl>https://github.com/AlexMacocian/SystemExtensions</RepositoryUrl>
|
||||||
|
<Description>Security extensions for the System namespace</Description>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
|||||||
@@ -0,0 +1,13 @@
|
|||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
using System.Logging;
|
||||||
|
|
||||||
|
namespace System.Extensions
|
||||||
|
{
|
||||||
|
public static class LoggingExtensions
|
||||||
|
{
|
||||||
|
public static ScopedLogger<T> CreateScopedLogger<T>(this ILogger<T> logger, string methodName, string flowIdentifier)
|
||||||
|
{
|
||||||
|
return ScopedLogger<T>.Create(logger, methodName, flowIdentifier);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,77 @@
|
|||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
using System.Extensions;
|
||||||
|
|
||||||
|
namespace System.Logging
|
||||||
|
{
|
||||||
|
public struct ScopedLogger<T>
|
||||||
|
{
|
||||||
|
private readonly ILogger<T> logger;
|
||||||
|
private readonly string scope;
|
||||||
|
private readonly string flowId;
|
||||||
|
|
||||||
|
private ScopedLogger(ILogger<T> logger, string scope, string flowId)
|
||||||
|
{
|
||||||
|
this.logger = logger;
|
||||||
|
this.scope = scope;
|
||||||
|
this.flowId = flowId.IsNullOrWhiteSpace() ? string.Empty : $"[{flowId}] ";
|
||||||
|
}
|
||||||
|
|
||||||
|
public ScopedLogger<T> LogInformation(string message, params object[] parameters)
|
||||||
|
{
|
||||||
|
this.logger.LogInformation(this.CreateMessage(message), parameters);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ScopedLogger<T> LogDebug(string message, params object[] parameters)
|
||||||
|
{
|
||||||
|
this.logger.LogDebug(this.CreateMessage(message), parameters);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ScopedLogger<T> LogWarning(string message, params object[] parameters)
|
||||||
|
{
|
||||||
|
this.logger.LogWarning(this.CreateMessage(message), parameters);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ScopedLogger<T> LogError(string message, params object[] parameters)
|
||||||
|
{
|
||||||
|
this.logger.LogError(this.CreateMessage(message), parameters);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ScopedLogger<T> LogCritical(string message, params object[] parameters)
|
||||||
|
{
|
||||||
|
this.logger.LogCritical(this.CreateMessage(message), parameters);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ScopedLogger<T> LogWarning(Exception e, string message, params object[] parameters)
|
||||||
|
{
|
||||||
|
this.logger.LogWarning(e, this.CreateMessage(message), parameters);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ScopedLogger<T> LogError(Exception e, string message, params object[] parameters)
|
||||||
|
{
|
||||||
|
this.logger.LogError(e, this.CreateMessage(message), parameters);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ScopedLogger<T> LogCritical(Exception e, string message, params object[] parameters)
|
||||||
|
{
|
||||||
|
this.logger.LogCritical(e, this.CreateMessage(message), parameters);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
private string CreateMessage(string message)
|
||||||
|
{
|
||||||
|
return $"{this.flowId}{this.scope}: {message}";
|
||||||
|
}
|
||||||
|
|
||||||
|
public static ScopedLogger<T> Create(ILogger<T> logger, string scope, string flowId)
|
||||||
|
{
|
||||||
|
return new ScopedLogger<T>(logger, scope.ThrowIfNull(nameof(scope)), flowId.ThrowIfNull(nameof(flowId)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -2,13 +2,15 @@
|
|||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFrameworks>netstandard2.0</TargetFrameworks>
|
<TargetFrameworks>netstandard2.0</TargetFrameworks>
|
||||||
|
<LangVersion>latest</LangVersion>
|
||||||
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
|
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
|
||||||
<PackageLicenseFile>LICENSE</PackageLicenseFile>
|
<PackageLicenseFile>LICENSE</PackageLicenseFile>
|
||||||
<PackageRequireLicenseAcceptance>true</PackageRequireLicenseAcceptance>
|
<PackageRequireLicenseAcceptance>true</PackageRequireLicenseAcceptance>
|
||||||
<RootNamespace>System</RootNamespace>
|
<RootNamespace>System</RootNamespace>
|
||||||
<Version>1.3.1</Version>
|
<Version>1.4</Version>
|
||||||
<Authors>Alexandru Macocian</Authors>
|
<Authors>Alexandru Macocian</Authors>
|
||||||
<RepositoryUrl>https://github.com/AlexMacocian/SystemExtensions</RepositoryUrl>
|
<RepositoryUrl>https://github.com/AlexMacocian/SystemExtensions</RepositoryUrl>
|
||||||
|
<Description>Extensions for the System namespace</Description>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
@@ -19,6 +21,7 @@
|
|||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="6.0.0" />
|
||||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.1" />
|
<PackageReference Include="Newtonsoft.Json" Version="13.0.1" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,42 @@
|
|||||||
|
using FluentAssertions;
|
||||||
|
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||||
|
|
||||||
|
namespace System.Extensions.Tests;
|
||||||
|
|
||||||
|
[TestClass]
|
||||||
|
public class ObjectExtensionsTests
|
||||||
|
{
|
||||||
|
[TestMethod]
|
||||||
|
public void ThrowIfNull_NetCore_ThrowsWithCorrectName()
|
||||||
|
{
|
||||||
|
object obj = null;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
System.Core.Extensions.ObjectExtensions.ThrowIfNull(obj);
|
||||||
|
}
|
||||||
|
catch (ArgumentNullException ex)
|
||||||
|
{
|
||||||
|
ex.ParamName.Should().Be("obj");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Assert.Fail("Null object should throw");
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void ThrowIfNull_NetStandard_ThrowsWithCorrectName()
|
||||||
|
{
|
||||||
|
object obj = null;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
ObjectExtensions.ThrowIfNull(obj, nameof(obj));
|
||||||
|
}
|
||||||
|
catch (ArgumentNullException ex)
|
||||||
|
{
|
||||||
|
ex.ParamName.Should().Be("obj");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Assert.Fail("Null object should throw");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,7 +1,5 @@
|
|||||||
using FluentAssertions;
|
using FluentAssertions;
|
||||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||||
using System;
|
|
||||||
using System.Http;
|
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Net;
|
using System.Net;
|
||||||
using System.Net.Http;
|
using System.Net.Http;
|
||||||
@@ -9,7 +7,7 @@ using System.Threading;
|
|||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using SystemExtensionsTests.Utils;
|
using SystemExtensionsTests.Utils;
|
||||||
|
|
||||||
namespace SystemExtensionsTests.Http
|
namespace System.Http.Tests
|
||||||
{
|
{
|
||||||
[TestClass]
|
[TestClass]
|
||||||
public class HttpClientTests
|
public class HttpClientTests
|
||||||
|
|||||||
@@ -0,0 +1,27 @@
|
|||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
namespace SystemExtensions.NetStandard.Tests.Logging.Models
|
||||||
|
{
|
||||||
|
public sealed class CachingLogger<T> : ILogger<T>
|
||||||
|
{
|
||||||
|
public List<string> LogCache { get; } = new();
|
||||||
|
|
||||||
|
public IDisposable BeginScope<TState>(TState state)
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool IsEnabled(LogLevel logLevel)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func<TState, Exception, string> formatter)
|
||||||
|
{
|
||||||
|
var message = formatter(state, exception);
|
||||||
|
this.LogCache.Add(message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,266 @@
|
|||||||
|
using System.Extensions;
|
||||||
|
using System.Linq;
|
||||||
|
using FluentAssertions;
|
||||||
|
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||||
|
using SystemExtensions.NetStandard.Tests.Logging.Models;
|
||||||
|
|
||||||
|
namespace System.Logging.Tests
|
||||||
|
{
|
||||||
|
[TestClass]
|
||||||
|
public class ScopedLoggerTests
|
||||||
|
{
|
||||||
|
private const string Flow = "Flow";
|
||||||
|
private const string Message = "Some message";
|
||||||
|
private readonly CachingLogger<ScopedLoggerTests> cachingLogger = new();
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void CreateLogger_CreatesNewLogger()
|
||||||
|
{
|
||||||
|
var scopedLogger = this.cachingLogger.CreateScopedLogger(nameof(this.CreateLogger_CreatesNewLogger), Flow);
|
||||||
|
scopedLogger.Should().BeOfType<ScopedLogger<ScopedLoggerTests>>();
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void CreateLogger_NullScope_ThrowsArgumentNullException()
|
||||||
|
{
|
||||||
|
var action = new Action(() =>
|
||||||
|
{
|
||||||
|
var scopedLogger = this.cachingLogger.CreateScopedLogger(null, Flow);
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
action.Should().Throw<ArgumentNullException>();
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void CreateLogger_NullFlow_ThrowsArgumentNullException()
|
||||||
|
{
|
||||||
|
var action = new Action(() =>
|
||||||
|
{
|
||||||
|
var scopedLogger = this.cachingLogger.CreateScopedLogger(nameof(this.CreateLogger_NullFlow_ThrowsArgumentNullException), null);
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
action.Should().Throw<ArgumentNullException>();
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void LogInformation_LogsExpected()
|
||||||
|
{
|
||||||
|
var scopedLogger = this.cachingLogger.CreateScopedLogger(nameof(this.LogInformation_LogsExpected), Flow);
|
||||||
|
var expectedMessage = $"[{Flow}] {nameof(this.LogInformation_LogsExpected)}: {Message}";
|
||||||
|
|
||||||
|
scopedLogger.LogInformation(Message);
|
||||||
|
|
||||||
|
this.cachingLogger.LogCache.Should().HaveCount(1);
|
||||||
|
this.cachingLogger.LogCache.First().Should().Be(expectedMessage);
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void LogInformation_EmptyFlow_LogsExpected()
|
||||||
|
{
|
||||||
|
var scopedLogger = this.cachingLogger.CreateScopedLogger(nameof(this.LogInformation_LogsExpected), string.Empty);
|
||||||
|
var expectedMessage = $"{nameof(this.LogInformation_LogsExpected)}: {Message}";
|
||||||
|
|
||||||
|
scopedLogger.LogInformation(Message);
|
||||||
|
|
||||||
|
this.cachingLogger.LogCache.Should().HaveCount(1);
|
||||||
|
this.cachingLogger.LogCache.First().Should().Be(expectedMessage);
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void LogInformation_WhitespaceFlow_LogsExpected()
|
||||||
|
{
|
||||||
|
var scopedLogger = this.cachingLogger.CreateScopedLogger(nameof(this.LogInformation_LogsExpected), " ");
|
||||||
|
var expectedMessage = $"{nameof(this.LogInformation_LogsExpected)}: {Message}";
|
||||||
|
|
||||||
|
scopedLogger.LogInformation(Message);
|
||||||
|
|
||||||
|
this.cachingLogger.LogCache.Should().HaveCount(1);
|
||||||
|
this.cachingLogger.LogCache.First().Should().Be(expectedMessage);
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void LogDebug_LogsExpected()
|
||||||
|
{
|
||||||
|
var scopedLogger = this.cachingLogger.CreateScopedLogger(nameof(this.LogDebug_LogsExpected), Flow);
|
||||||
|
var expectedMessage = $"[{Flow}] {nameof(this.LogDebug_LogsExpected)}: {Message}";
|
||||||
|
|
||||||
|
scopedLogger.LogDebug(Message);
|
||||||
|
|
||||||
|
this.cachingLogger.LogCache.Should().HaveCount(1);
|
||||||
|
this.cachingLogger.LogCache.First().Should().Be(expectedMessage);
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void LogDebug_EmptyFlow_LogsExpected()
|
||||||
|
{
|
||||||
|
var scopedLogger = this.cachingLogger.CreateScopedLogger(nameof(this.LogDebug_EmptyFlow_LogsExpected), string.Empty);
|
||||||
|
var expectedMessage = $"{nameof(this.LogDebug_EmptyFlow_LogsExpected)}: {Message}";
|
||||||
|
|
||||||
|
scopedLogger.LogDebug(Message);
|
||||||
|
|
||||||
|
this.cachingLogger.LogCache.Should().HaveCount(1);
|
||||||
|
this.cachingLogger.LogCache.First().Should().Be(expectedMessage);
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void LogDebug_WhitespaceFlow_LogsExpected()
|
||||||
|
{
|
||||||
|
var scopedLogger = this.cachingLogger.CreateScopedLogger(nameof(this.LogDebug_WhitespaceFlow_LogsExpected), " ");
|
||||||
|
var expectedMessage = $"{nameof(this.LogDebug_WhitespaceFlow_LogsExpected)}: {Message}";
|
||||||
|
|
||||||
|
scopedLogger.LogDebug(Message);
|
||||||
|
|
||||||
|
this.cachingLogger.LogCache.Should().HaveCount(1);
|
||||||
|
this.cachingLogger.LogCache.First().Should().Be(expectedMessage);
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void LogWarning_LogsExpected()
|
||||||
|
{
|
||||||
|
var scopedLogger = this.cachingLogger.CreateScopedLogger(nameof(this.LogWarning_LogsExpected), Flow);
|
||||||
|
var expectedMessage = $"[{Flow}] {nameof(this.LogWarning_LogsExpected)}: {Message}";
|
||||||
|
|
||||||
|
scopedLogger.LogWarning(Message);
|
||||||
|
|
||||||
|
this.cachingLogger.LogCache.Should().HaveCount(1);
|
||||||
|
this.cachingLogger.LogCache.First().Should().Be(expectedMessage);
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void LogWarning_EmptyFlow_LogsExpected()
|
||||||
|
{
|
||||||
|
var scopedLogger = this.cachingLogger.CreateScopedLogger(nameof(this.LogWarning_EmptyFlow_LogsExpected), string.Empty);
|
||||||
|
var expectedMessage = $"{nameof(this.LogWarning_EmptyFlow_LogsExpected)}: {Message}";
|
||||||
|
|
||||||
|
scopedLogger.LogWarning(Message);
|
||||||
|
|
||||||
|
this.cachingLogger.LogCache.Should().HaveCount(1);
|
||||||
|
this.cachingLogger.LogCache.First().Should().Be(expectedMessage);
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void LogWarning_WhitespaceFlow_LogsExpected()
|
||||||
|
{
|
||||||
|
var scopedLogger = this.cachingLogger.CreateScopedLogger(nameof(this.LogWarning_WhitespaceFlow_LogsExpected), " ");
|
||||||
|
var expectedMessage = $"{nameof(this.LogWarning_WhitespaceFlow_LogsExpected)}: {Message}";
|
||||||
|
|
||||||
|
scopedLogger.LogWarning(Message);
|
||||||
|
|
||||||
|
this.cachingLogger.LogCache.Should().HaveCount(1);
|
||||||
|
this.cachingLogger.LogCache.First().Should().Be(expectedMessage);
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void LogError_LogsExpected()
|
||||||
|
{
|
||||||
|
var scopedLogger = this.cachingLogger.CreateScopedLogger(nameof(this.LogError_LogsExpected), Flow);
|
||||||
|
var expectedMessage = $"[{Flow}] {nameof(this.LogError_LogsExpected)}: {Message}";
|
||||||
|
|
||||||
|
scopedLogger.LogError(Message);
|
||||||
|
|
||||||
|
this.cachingLogger.LogCache.Should().HaveCount(1);
|
||||||
|
this.cachingLogger.LogCache.First().Should().Be(expectedMessage);
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void LogError_EmptyFlow_LogsExpected()
|
||||||
|
{
|
||||||
|
var scopedLogger = this.cachingLogger.CreateScopedLogger(nameof(this.LogError_EmptyFlow_LogsExpected), string.Empty);
|
||||||
|
var expectedMessage = $"{nameof(this.LogError_EmptyFlow_LogsExpected)}: {Message}";
|
||||||
|
|
||||||
|
scopedLogger.LogError(Message);
|
||||||
|
|
||||||
|
this.cachingLogger.LogCache.Should().HaveCount(1);
|
||||||
|
this.cachingLogger.LogCache.First().Should().Be(expectedMessage);
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void LogError_WhitespaceFlow_LogsExpected()
|
||||||
|
{
|
||||||
|
var scopedLogger = this.cachingLogger.CreateScopedLogger(nameof(this.LogError_WhitespaceFlow_LogsExpected), " ");
|
||||||
|
var expectedMessage = $"{nameof(this.LogError_WhitespaceFlow_LogsExpected)}: {Message}";
|
||||||
|
|
||||||
|
scopedLogger.LogError(Message);
|
||||||
|
|
||||||
|
this.cachingLogger.LogCache.Should().HaveCount(1);
|
||||||
|
this.cachingLogger.LogCache.First().Should().Be(expectedMessage);
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void LogCritical_LogsExpected()
|
||||||
|
{
|
||||||
|
var scopedLogger = this.cachingLogger.CreateScopedLogger(nameof(this.LogCritical_LogsExpected), Flow);
|
||||||
|
var expectedMessage = $"[{Flow}] {nameof(this.LogCritical_LogsExpected)}: {Message}";
|
||||||
|
|
||||||
|
scopedLogger.LogCritical(Message);
|
||||||
|
|
||||||
|
this.cachingLogger.LogCache.Should().HaveCount(1);
|
||||||
|
this.cachingLogger.LogCache.First().Should().Be(expectedMessage);
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void LogCritical_EmptyFlow_LogsExpected()
|
||||||
|
{
|
||||||
|
var scopedLogger = this.cachingLogger.CreateScopedLogger(nameof(this.LogCritical_EmptyFlow_LogsExpected), string.Empty);
|
||||||
|
var expectedMessage = $"{nameof(this.LogCritical_EmptyFlow_LogsExpected)}: {Message}";
|
||||||
|
|
||||||
|
scopedLogger.LogCritical(Message);
|
||||||
|
|
||||||
|
this.cachingLogger.LogCache.Should().HaveCount(1);
|
||||||
|
this.cachingLogger.LogCache.First().Should().Be(expectedMessage);
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void LogCritical_WhitespaceFlow_LogsExpected()
|
||||||
|
{
|
||||||
|
var scopedLogger = this.cachingLogger.CreateScopedLogger(nameof(this.LogCritical_WhitespaceFlow_LogsExpected), " ");
|
||||||
|
var expectedMessage = $"{nameof(this.LogCritical_WhitespaceFlow_LogsExpected)}: {Message}";
|
||||||
|
|
||||||
|
scopedLogger.LogCritical(Message);
|
||||||
|
|
||||||
|
this.cachingLogger.LogCache.Should().HaveCount(1);
|
||||||
|
this.cachingLogger.LogCache.First().Should().Be(expectedMessage);
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void LogWarning_WithException_LogsExpected()
|
||||||
|
{
|
||||||
|
var exception = new Exception();
|
||||||
|
var scopedLogger = this.cachingLogger.CreateScopedLogger(nameof(this.LogWarning_WithException_LogsExpected), Flow);
|
||||||
|
var expectedMessage = $"[{Flow}] {nameof(this.LogWarning_WithException_LogsExpected)}: {Message}";
|
||||||
|
|
||||||
|
scopedLogger.LogWarning(exception, Message);
|
||||||
|
|
||||||
|
this.cachingLogger.LogCache.Should().HaveCount(1);
|
||||||
|
this.cachingLogger.LogCache.First().Should().Be(expectedMessage);
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void LogError_WithException_LogsExpected()
|
||||||
|
{
|
||||||
|
var exception = new Exception();
|
||||||
|
var scopedLogger = this.cachingLogger.CreateScopedLogger(nameof(this.LogError_WithException_LogsExpected), Flow);
|
||||||
|
var expectedMessage = $"[{Flow}] {nameof(this.LogError_WithException_LogsExpected)}: {Message}";
|
||||||
|
|
||||||
|
scopedLogger.LogError(exception, Message);
|
||||||
|
|
||||||
|
this.cachingLogger.LogCache.Should().HaveCount(1);
|
||||||
|
this.cachingLogger.LogCache.First().Should().Be(expectedMessage);
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void LogCritical_WithException_LogsExpected()
|
||||||
|
{
|
||||||
|
var exception = new Exception();
|
||||||
|
var scopedLogger = this.cachingLogger.CreateScopedLogger(nameof(this.LogCritical_WithException_LogsExpected), Flow);
|
||||||
|
var expectedMessage = $"[{Flow}] {nameof(this.LogCritical_WithException_LogsExpected)}: {Message}";
|
||||||
|
|
||||||
|
scopedLogger.LogCritical(exception, Message);
|
||||||
|
|
||||||
|
this.cachingLogger.LogCache.Should().HaveCount(1);
|
||||||
|
this.cachingLogger.LogCache.First().Should().Be(expectedMessage);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
<Project Sdk="Microsoft.NET.Sdk">
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFramework>net5.0</TargetFramework>
|
<TargetFramework>net6.0</TargetFramework>
|
||||||
|
|
||||||
<IsPackable>false</IsPackable>
|
<IsPackable>false</IsPackable>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
@@ -18,6 +18,7 @@
|
|||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\SystemExtensions.NetCore\SystemExtensions.NetCore.csproj" />
|
||||||
<ProjectReference Include="..\SystemExtensions.NetStandard\SystemExtensions.NetStandard.csproj" />
|
<ProjectReference Include="..\SystemExtensions.NetStandard\SystemExtensions.NetStandard.csproj" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
|
|||||||
+21
-4
@@ -1,7 +1,7 @@
|
|||||||
|
|
||||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||||
# Visual Studio Version 16
|
# Visual Studio Version 17
|
||||||
VisualStudioVersion = 16.0.31005.135
|
VisualStudioVersion = 17.0.31815.197
|
||||||
MinimumVisualStudioVersion = 10.0.40219.1
|
MinimumVisualStudioVersion = 10.0.40219.1
|
||||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SystemExtensions.NetStandard", "SystemExtensions.NetStandard\SystemExtensions.NetStandard.csproj", "{4CE9E55C-6016-4631-B8D4-4D763DD05F23}"
|
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SystemExtensions.NetStandard", "SystemExtensions.NetStandard\SystemExtensions.NetStandard.csproj", "{4CE9E55C-6016-4631-B8D4-4D763DD05F23}"
|
||||||
EndProject
|
EndProject
|
||||||
@@ -14,9 +14,19 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SystemExtensions.NetStandar
|
|||||||
EndProject
|
EndProject
|
||||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SystemExtensions.NetStandard.DependencyInjection.Tests", "SystemExtensions.DependencyInjection.Tests\SystemExtensions.NetStandard.DependencyInjection.Tests.csproj", "{1AFA1EEF-CEBA-4046-8466-C9B52979B7DA}"
|
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SystemExtensions.NetStandard.DependencyInjection.Tests", "SystemExtensions.DependencyInjection.Tests\SystemExtensions.NetStandard.DependencyInjection.Tests.csproj", "{1AFA1EEF-CEBA-4046-8466-C9B52979B7DA}"
|
||||||
EndProject
|
EndProject
|
||||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SystemExtensions.NetStandard.Security", "SystemExtensions.NetStandard.Security\SystemExtensions.NetStandard.Security.csproj", "{BFC5BEE7-2569-45EA-9713-CDB32EED57AA}"
|
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SystemExtensions.NetStandard.Security", "SystemExtensions.NetStandard.Security\SystemExtensions.NetStandard.Security.csproj", "{BFC5BEE7-2569-45EA-9713-CDB32EED57AA}"
|
||||||
EndProject
|
EndProject
|
||||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SystemExtensions.NetStandard.Security.Tests", "SystemExtensions.NetStandard.Security.Tests\SystemExtensions.NetStandard.Security.Tests.csproj", "{341ECE0F-A8DD-49A0-AE3D-B28D72E85130}"
|
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SystemExtensions.NetStandard.Security.Tests", "SystemExtensions.NetStandard.Security.Tests\SystemExtensions.NetStandard.Security.Tests.csproj", "{341ECE0F-A8DD-49A0-AE3D-B28D72E85130}"
|
||||||
|
EndProject
|
||||||
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SystemExtensions.NetCore", "SystemExtensions.NetCore\SystemExtensions.NetCore.csproj", "{9E6EBBF0-671B-4941-A72B-2CA60C882038}"
|
||||||
|
EndProject
|
||||||
|
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = ".github", ".github", "{BBA334F6-779A-4A45-8BF3-EA321D0D12CF}"
|
||||||
|
EndProject
|
||||||
|
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "workflows", "workflows", "{52962B0C-BB5F-4211-A70C-801D0A73CACC}"
|
||||||
|
ProjectSection(SolutionItems) = preProject
|
||||||
|
.github\workflows\cd.yaml = .github\workflows\cd.yaml
|
||||||
|
.github\workflows\ci.yaml = .github\workflows\ci.yaml
|
||||||
|
EndProjectSection
|
||||||
EndProject
|
EndProject
|
||||||
Global
|
Global
|
||||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||||
@@ -48,10 +58,17 @@ Global
|
|||||||
{341ECE0F-A8DD-49A0-AE3D-B28D72E85130}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
{341ECE0F-A8DD-49A0-AE3D-B28D72E85130}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
{341ECE0F-A8DD-49A0-AE3D-B28D72E85130}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
{341ECE0F-A8DD-49A0-AE3D-B28D72E85130}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
{341ECE0F-A8DD-49A0-AE3D-B28D72E85130}.Release|Any CPU.Build.0 = Release|Any CPU
|
{341ECE0F-A8DD-49A0-AE3D-B28D72E85130}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{9E6EBBF0-671B-4941-A72B-2CA60C882038}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{9E6EBBF0-671B-4941-A72B-2CA60C882038}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{9E6EBBF0-671B-4941-A72B-2CA60C882038}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{9E6EBBF0-671B-4941-A72B-2CA60C882038}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
EndGlobalSection
|
EndGlobalSection
|
||||||
GlobalSection(SolutionProperties) = preSolution
|
GlobalSection(SolutionProperties) = preSolution
|
||||||
HideSolutionNode = FALSE
|
HideSolutionNode = FALSE
|
||||||
EndGlobalSection
|
EndGlobalSection
|
||||||
|
GlobalSection(NestedProjects) = preSolution
|
||||||
|
{52962B0C-BB5F-4211-A70C-801D0A73CACC} = {BBA334F6-779A-4A45-8BF3-EA321D0D12CF}
|
||||||
|
EndGlobalSection
|
||||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||||
SolutionGuid = {BFA90E90-BD97-40AD-B19E-C723E504369A}
|
SolutionGuid = {BFA90E90-BD97-40AD-B19E-C723E504369A}
|
||||||
EndGlobalSection
|
EndGlobalSection
|
||||||
|
|||||||
Reference in New Issue
Block a user