Solution restructured to use multiple test projects
This commit is contained in:
@@ -0,0 +1,11 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\src\AMWD.Common.AspNetCore\AMWD.Common.AspNetCore.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
@@ -0,0 +1,352 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Net;
|
||||
using System.Security.Claims;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using AMWD.Common.AspNetCore.Security.BasicAuthentication;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.Mvc.Abstractions;
|
||||
using Microsoft.AspNetCore.Mvc.Filters;
|
||||
using Microsoft.AspNetCore.Routing;
|
||||
using Microsoft.Extensions.Primitives;
|
||||
using Moq;
|
||||
|
||||
namespace AMWD.Common.AspNetCore.Tests.Attributes
|
||||
{
|
||||
[TestClass]
|
||||
public class BasicAuthenticationAttributeTest
|
||||
{
|
||||
private Mock<IHeaderDictionary> _requestHeaderMock;
|
||||
private Mock<IHeaderDictionary> _responseHeaderMock;
|
||||
|
||||
private Mock<HttpRequest> _requestMock;
|
||||
private Mock<HttpResponse> _responseMock;
|
||||
|
||||
private Mock<HttpContext> _contextMock;
|
||||
|
||||
private Dictionary<string, string> _requestHeaders;
|
||||
private string _validatorRealm;
|
||||
private ClaimsPrincipal _validatorResult;
|
||||
|
||||
private string _responseHeaderAuthCallback;
|
||||
|
||||
[TestInitialize]
|
||||
public void InitializeTest()
|
||||
{
|
||||
_requestHeaders = [];
|
||||
_validatorRealm = null;
|
||||
_validatorResult = null;
|
||||
|
||||
_responseHeaderAuthCallback = null;
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public async Task ShouldValidateViaUsernamePassword()
|
||||
{
|
||||
// arrange
|
||||
var attribute = new BasicAuthenticationAttribute
|
||||
{
|
||||
Username = "user",
|
||||
Password = "password"
|
||||
};
|
||||
_requestHeaders.Add("Authorization", $"Basic {Convert.ToBase64String(Encoding.UTF8.GetBytes($"{attribute.Username}:{attribute.Password}"))}");
|
||||
|
||||
var context = GetContext();
|
||||
|
||||
// act
|
||||
await attribute.OnAuthorizationAsync(context);
|
||||
|
||||
// assert
|
||||
Assert.IsNull(context.Result);
|
||||
Assert.IsTrue(string.IsNullOrWhiteSpace(_responseHeaderAuthCallback));
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public async Task ShouldValidateViaValidator()
|
||||
{
|
||||
// arrange
|
||||
var attribute = new BasicAuthenticationAttribute();
|
||||
_requestHeaders.Add("Authorization", $"Basic {Convert.ToBase64String(Encoding.UTF8.GetBytes($"{attribute.Username}:{attribute.Password}"))}");
|
||||
_validatorResult = new ClaimsPrincipal();
|
||||
|
||||
var context = GetContext(hasValidator: true);
|
||||
|
||||
// act
|
||||
await attribute.OnAuthorizationAsync(context);
|
||||
|
||||
// assert
|
||||
Assert.IsNull(context.Result);
|
||||
Assert.IsTrue(string.IsNullOrWhiteSpace(_responseHeaderAuthCallback));
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public async Task ShouldAllowAnonymous()
|
||||
{
|
||||
// arrange
|
||||
var attribute = new BasicAuthenticationAttribute
|
||||
{
|
||||
Username = "user",
|
||||
Password = "password"
|
||||
};
|
||||
var context = GetContext(isAnonymousAllowed: true);
|
||||
|
||||
// act
|
||||
await attribute.OnAuthorizationAsync(context);
|
||||
|
||||
// assert
|
||||
Assert.IsNull(context.Result);
|
||||
Assert.IsTrue(string.IsNullOrWhiteSpace(_responseHeaderAuthCallback));
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public async Task ShouldAskOnUsernamePasswordWithoutRealm()
|
||||
{
|
||||
// arrange
|
||||
var attribute = new BasicAuthenticationAttribute
|
||||
{
|
||||
Username = "user",
|
||||
Password = "password"
|
||||
};
|
||||
var context = GetContext();
|
||||
|
||||
// act
|
||||
await attribute.OnAuthorizationAsync(context);
|
||||
|
||||
// assert
|
||||
Assert.IsNotNull(context.Result);
|
||||
Assert.IsTrue(context.Result is StatusCodeResult);
|
||||
Assert.AreEqual(401, ((StatusCodeResult)context.Result).StatusCode);
|
||||
|
||||
Assert.IsFalse(string.IsNullOrWhiteSpace(_responseHeaderAuthCallback));
|
||||
Assert.AreEqual("Basic", _responseHeaderAuthCallback);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public async Task ShouldAskOnUsernamePasswordWithRealm()
|
||||
{
|
||||
// arrange
|
||||
var attribute = new BasicAuthenticationAttribute
|
||||
{
|
||||
Username = "user",
|
||||
Password = "password",
|
||||
Realm = "re:al\"m"
|
||||
};
|
||||
var context = GetContext();
|
||||
|
||||
// act
|
||||
await attribute.OnAuthorizationAsync(context);
|
||||
|
||||
// assert
|
||||
Assert.IsNotNull(context.Result);
|
||||
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);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public async Task ShouldAskOnUsernamePasswordWrongUser()
|
||||
{
|
||||
// arrange
|
||||
var attribute = new BasicAuthenticationAttribute
|
||||
{
|
||||
Username = "user",
|
||||
Password = "password"
|
||||
};
|
||||
_requestHeaders.Add("Authorization", $"Basic {Convert.ToBase64String(Encoding.UTF8.GetBytes($"{attribute.Username}a:{attribute.Password}"))}");
|
||||
var context = GetContext();
|
||||
|
||||
// act
|
||||
await attribute.OnAuthorizationAsync(context);
|
||||
|
||||
// assert
|
||||
Assert.IsNotNull(context.Result);
|
||||
Assert.IsTrue(context.Result is StatusCodeResult);
|
||||
Assert.AreEqual(401, ((StatusCodeResult)context.Result).StatusCode);
|
||||
|
||||
Assert.IsFalse(string.IsNullOrWhiteSpace(_responseHeaderAuthCallback));
|
||||
Assert.AreEqual("Basic", _responseHeaderAuthCallback);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public async Task ShouldAskOnUsernamePasswordWrongPassword()
|
||||
{
|
||||
// arrange
|
||||
var attribute = new BasicAuthenticationAttribute
|
||||
{
|
||||
Username = "user",
|
||||
Password = "password"
|
||||
};
|
||||
_requestHeaders.Add("Authorization", $"Basic {Convert.ToBase64String(Encoding.UTF8.GetBytes($"{attribute.Username}:{attribute.Password}a"))}");
|
||||
var context = GetContext();
|
||||
|
||||
// act
|
||||
await attribute.OnAuthorizationAsync(context);
|
||||
|
||||
// assert
|
||||
Assert.IsNotNull(context.Result);
|
||||
Assert.IsTrue(context.Result is StatusCodeResult);
|
||||
Assert.AreEqual(401, ((StatusCodeResult)context.Result).StatusCode);
|
||||
|
||||
Assert.IsFalse(string.IsNullOrWhiteSpace(_responseHeaderAuthCallback));
|
||||
Assert.AreEqual("Basic", _responseHeaderAuthCallback);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public async Task ShouldAskOnValidatorWithRealmOnAttribute()
|
||||
{
|
||||
// arrange
|
||||
var attribute = new BasicAuthenticationAttribute
|
||||
{
|
||||
Realm = "attribute"
|
||||
};
|
||||
_requestHeaders.Add("Authorization", $"Basic {Convert.ToBase64String(Encoding.UTF8.GetBytes($"{attribute.Username}:{attribute.Password}"))}");
|
||||
var context = GetContext(hasValidator: true);
|
||||
|
||||
// act
|
||||
await attribute.OnAuthorizationAsync(context);
|
||||
|
||||
// assert
|
||||
Assert.IsNotNull(context.Result);
|
||||
Assert.IsTrue(context.Result is StatusCodeResult);
|
||||
Assert.AreEqual(401, ((StatusCodeResult)context.Result).StatusCode);
|
||||
|
||||
Assert.IsFalse(string.IsNullOrWhiteSpace(_responseHeaderAuthCallback));
|
||||
Assert.AreEqual("Basic realm=\"attribute\"", _responseHeaderAuthCallback);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public async Task ShouldAskOnValidatorWithRealmOnValidator()
|
||||
{
|
||||
// arrange
|
||||
_validatorRealm = "validator";
|
||||
var attribute = new BasicAuthenticationAttribute();
|
||||
_requestHeaders.Add("Authorization", $"Basic {Convert.ToBase64String(Encoding.UTF8.GetBytes($"{attribute.Username}:{attribute.Password}"))}");
|
||||
var context = GetContext(hasValidator: true);
|
||||
|
||||
// act
|
||||
await attribute.OnAuthorizationAsync(context);
|
||||
|
||||
// assert
|
||||
Assert.IsNotNull(context.Result);
|
||||
Assert.IsTrue(context.Result is StatusCodeResult);
|
||||
Assert.AreEqual(401, ((StatusCodeResult)context.Result).StatusCode);
|
||||
|
||||
Assert.IsFalse(string.IsNullOrWhiteSpace(_responseHeaderAuthCallback));
|
||||
Assert.AreEqual("Basic realm=\"validator\"", _responseHeaderAuthCallback);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public async Task ShouldReturnInternalError()
|
||||
{
|
||||
// arrange
|
||||
var attribute = new BasicAuthenticationAttribute();
|
||||
_requestHeaders.Add("Authorization", $"Basic {Convert.ToBase64String(Encoding.UTF8.GetBytes($"{attribute.Username}"))}");
|
||||
var context = GetContext();
|
||||
|
||||
// act
|
||||
await attribute.OnAuthorizationAsync(context);
|
||||
|
||||
// assert
|
||||
Assert.IsNotNull(context.Result);
|
||||
Assert.IsTrue(context.Result is StatusCodeResult);
|
||||
Assert.AreEqual(500, ((StatusCodeResult)context.Result).StatusCode);
|
||||
}
|
||||
|
||||
private AuthorizationFilterContext GetContext(bool isAnonymousAllowed = false, bool hasValidator = false)
|
||||
{
|
||||
_requestHeaderMock = new Mock<IHeaderDictionary>();
|
||||
foreach (var header in _requestHeaders)
|
||||
{
|
||||
StringValues outVal = header.Value;
|
||||
_requestHeaderMock
|
||||
.Setup(h => h.ContainsKey(header.Key))
|
||||
.Returns(true);
|
||||
_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.WWWAuthenticate = It.IsAny<StringValues>())
|
||||
.Callback<StringValues>((value) =>
|
||||
{
|
||||
_responseHeaderAuthCallback = value;
|
||||
});
|
||||
|
||||
_requestMock = new Mock<HttpRequest>();
|
||||
_requestMock
|
||||
.Setup(r => r.Headers)
|
||||
.Returns(_requestHeaderMock.Object);
|
||||
|
||||
_responseMock = new Mock<HttpResponse>();
|
||||
_responseMock
|
||||
.Setup(r => r.Headers)
|
||||
.Returns(_responseHeaderMock.Object);
|
||||
|
||||
var requestServicesMock = new Mock<IServiceProvider>();
|
||||
|
||||
if (hasValidator)
|
||||
{
|
||||
var validatorMock = new Mock<IBasicAuthenticationValidator>();
|
||||
validatorMock
|
||||
.Setup(v => v.Realm)
|
||||
.Returns(_validatorRealm);
|
||||
validatorMock
|
||||
.Setup(v => v.ValidateAsync(It.IsAny<string>(), It.IsAny<string>(), It.IsAny<IPAddress>(), It.IsAny<CancellationToken>()))
|
||||
.ReturnsAsync(_validatorResult);
|
||||
|
||||
requestServicesMock
|
||||
.Setup(rs => rs.GetService(typeof(IBasicAuthenticationValidator)))
|
||||
.Returns(validatorMock.Object);
|
||||
}
|
||||
|
||||
var connectionInfoMock = new Mock<ConnectionInfo>();
|
||||
connectionInfoMock
|
||||
.Setup(ci => ci.RemoteIpAddress)
|
||||
.Returns(IPAddress.Loopback);
|
||||
|
||||
_contextMock = new Mock<HttpContext>();
|
||||
_contextMock
|
||||
.Setup(c => c.Request)
|
||||
.Returns(_requestMock.Object);
|
||||
_contextMock
|
||||
.Setup(c => c.Response)
|
||||
.Returns(_responseMock.Object);
|
||||
_contextMock
|
||||
.Setup(c => c.RequestServices)
|
||||
.Returns(requestServicesMock.Object);
|
||||
_contextMock
|
||||
.Setup(c => c.Connection)
|
||||
.Returns(connectionInfoMock.Object);
|
||||
_contextMock
|
||||
.Setup(c => c.RequestAborted)
|
||||
.Returns(CancellationToken.None);
|
||||
|
||||
var routeDataMock = new Mock<RouteData>();
|
||||
|
||||
var actionDescriptor = new ActionDescriptor
|
||||
{
|
||||
EndpointMetadata = new List<object>()
|
||||
};
|
||||
if (isAnonymousAllowed)
|
||||
actionDescriptor.EndpointMetadata.Add(new AllowAnonymousAttribute());
|
||||
|
||||
return new AuthorizationFilterContext(new ActionContext
|
||||
{
|
||||
HttpContext = _contextMock.Object,
|
||||
RouteData = routeDataMock.Object,
|
||||
ActionDescriptor = actionDescriptor,
|
||||
}, new List<IFilterMetadata>());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,339 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Net;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.Mvc.Abstractions;
|
||||
using Microsoft.AspNetCore.Mvc.Filters;
|
||||
using Microsoft.AspNetCore.Routing;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Moq;
|
||||
|
||||
namespace AMWD.Common.AspNetCore.Tests.Attributes
|
||||
{
|
||||
[TestClass]
|
||||
public class IPAllowListAttributeTest
|
||||
{
|
||||
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 = [];
|
||||
_itemsCallback = [];
|
||||
_configKey = null;
|
||||
_configExists = false;
|
||||
_allowedIpsConfig = [];
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ShouldDenyOnNoConfiguration()
|
||||
{
|
||||
// arrange
|
||||
var remote = IPAddress.Parse("192.168.178.1");
|
||||
var attribute = new IPAllowListAttribute();
|
||||
var context = GetContext(remote);
|
||||
|
||||
// act
|
||||
attribute.OnActionExecuting(context);
|
||||
|
||||
// assert
|
||||
Assert.IsNotNull(context.Result);
|
||||
Assert.IsTrue(context.Result is StatusCodeResult);
|
||||
Assert.AreEqual(403, ((StatusCodeResult)context.Result).StatusCode);
|
||||
|
||||
Assert.AreEqual(1, _itemsCallback.Count);
|
||||
Assert.AreEqual(remote, _itemsCallback["RemoteAddress"]);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ShouldDenyOnWrongConfiguration()
|
||||
{
|
||||
// arrange
|
||||
var remote = IPAddress.Parse("192.168.178.1");
|
||||
var attribute = new IPAllowListAttribute
|
||||
{
|
||||
AllowedIpAddresses = "192.168.178:1"
|
||||
};
|
||||
var context = GetContext(remote);
|
||||
|
||||
// act
|
||||
attribute.OnActionExecuting(context);
|
||||
|
||||
// assert
|
||||
Assert.IsNotNull(context.Result);
|
||||
Assert.IsTrue(context.Result is StatusCodeResult);
|
||||
Assert.AreEqual(403, ((StatusCodeResult)context.Result).StatusCode);
|
||||
|
||||
Assert.AreEqual(1, _itemsCallback.Count);
|
||||
Assert.AreEqual(remote, _itemsCallback["RemoteAddress"]);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ShouldAllowLocalAccess()
|
||||
{
|
||||
// arrange
|
||||
var attribute = new IPAllowListAttribute();
|
||||
var context = GetContext();
|
||||
|
||||
// act
|
||||
attribute.OnActionExecuting(context);
|
||||
|
||||
// assert
|
||||
Assert.IsNull(context.Result);
|
||||
Assert.AreEqual(1, _itemsCallback.Count);
|
||||
Assert.AreEqual(IPAddress.Loopback, _itemsCallback["RemoteAddress"]);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ShouldDenyLocalAccess()
|
||||
{
|
||||
// arrange
|
||||
var attribute = new IPAllowListAttribute
|
||||
{
|
||||
AllowLocalAccess = false
|
||||
};
|
||||
var context = GetContext();
|
||||
|
||||
// act
|
||||
attribute.OnActionExecuting(context);
|
||||
|
||||
// assert
|
||||
Assert.IsNotNull(context.Result);
|
||||
Assert.IsTrue(context.Result is StatusCodeResult);
|
||||
Assert.AreEqual(403, ((StatusCodeResult)context.Result).StatusCode);
|
||||
|
||||
Assert.AreEqual(1, _itemsCallback.Count);
|
||||
Assert.AreEqual(IPAddress.Loopback, _itemsCallback["RemoteAddress"]);
|
||||
}
|
||||
|
||||
[DataTestMethod]
|
||||
[DataRow("192.168.178.10")]
|
||||
[DataRow("192.168.178.20")]
|
||||
public void ShouldAllowSpecificAddress(string address)
|
||||
{
|
||||
// arrange
|
||||
var remote = IPAddress.Parse(address);
|
||||
var attribute = new IPAllowListAttribute
|
||||
{
|
||||
AllowLocalAccess = false,
|
||||
AllowedIpAddresses = ",127.0.0.0/8,192.168.178.10"
|
||||
};
|
||||
var context = GetContext(remote);
|
||||
|
||||
// act
|
||||
attribute.OnActionExecuting(context);
|
||||
|
||||
// assert
|
||||
if (address == "192.168.178.10")
|
||||
{
|
||||
Assert.IsNull(context.Result);
|
||||
}
|
||||
else
|
||||
{
|
||||
Assert.IsNotNull(context.Result);
|
||||
Assert.IsTrue(context.Result is StatusCodeResult);
|
||||
Assert.AreEqual(403, ((StatusCodeResult)context.Result).StatusCode);
|
||||
}
|
||||
|
||||
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 IPAllowListAttribute
|
||||
{
|
||||
AllowLocalAccess = true,
|
||||
ConfigurationKey = _configKey
|
||||
};
|
||||
var context = GetContext();
|
||||
|
||||
// act
|
||||
attribute.OnActionExecuting(context);
|
||||
|
||||
// assert
|
||||
Assert.IsNull(context.Result);
|
||||
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 IPAllowListAttribute
|
||||
{
|
||||
AllowLocalAccess = false,
|
||||
ConfigurationKey = _configKey
|
||||
};
|
||||
var context = GetContext();
|
||||
|
||||
// act
|
||||
attribute.OnActionExecuting(context);
|
||||
|
||||
// assert
|
||||
Assert.IsNotNull(context.Result);
|
||||
Assert.IsTrue(context.Result is StatusCodeResult);
|
||||
Assert.AreEqual(403, ((StatusCodeResult)context.Result).StatusCode);
|
||||
|
||||
Assert.AreEqual(1, _itemsCallback.Count);
|
||||
Assert.AreEqual(IPAddress.Loopback, _itemsCallback["RemoteAddress"]);
|
||||
}
|
||||
|
||||
[DataTestMethod]
|
||||
[DataRow("192.168.178.10")]
|
||||
[DataRow("192.168.178.20")]
|
||||
public void ShouldAllowSpecificAddressConfig(string address)
|
||||
{
|
||||
// arrange
|
||||
_configKey = "White:List";
|
||||
_configExists = true;
|
||||
_allowedIpsConfig.Add("192.168.178.10");
|
||||
var attribute = new IPAllowListAttribute
|
||||
{
|
||||
AllowLocalAccess = false,
|
||||
ConfigurationKey = _configKey
|
||||
};
|
||||
var remote = IPAddress.Parse(address);
|
||||
var context = GetContext(remote);
|
||||
|
||||
// act
|
||||
attribute.OnActionExecuting(context);
|
||||
|
||||
// assert
|
||||
if (address == "192.168.178.10")
|
||||
{
|
||||
Assert.IsNull(context.Result);
|
||||
}
|
||||
else
|
||||
{
|
||||
Assert.IsNotNull(context.Result);
|
||||
Assert.IsTrue(context.Result is StatusCodeResult);
|
||||
Assert.AreEqual(403, ((StatusCodeResult)context.Result).StatusCode);
|
||||
}
|
||||
|
||||
Assert.AreEqual(1, _itemsCallback.Count);
|
||||
Assert.AreEqual(remote, _itemsCallback["RemoteAddress"]);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ShouldDenyOnMissingConfiguration()
|
||||
{
|
||||
// arrange
|
||||
_configKey = "White:List";
|
||||
_configExists = false;
|
||||
var attribute = new IPAllowListAttribute
|
||||
{
|
||||
AllowLocalAccess = false,
|
||||
ConfigurationKey = _configKey
|
||||
};
|
||||
var context = GetContext();
|
||||
|
||||
// act
|
||||
attribute.OnActionExecuting(context);
|
||||
|
||||
// assert
|
||||
Assert.IsNotNull(context.Result);
|
||||
Assert.IsTrue(context.Result is StatusCodeResult);
|
||||
Assert.AreEqual(403, ((StatusCodeResult)context.Result).StatusCode);
|
||||
|
||||
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)
|
||||
{
|
||||
requestHeaderMock
|
||||
.Setup(h => h.ContainsKey(header.Key))
|
||||
.Returns(true);
|
||||
requestHeaderMock
|
||||
.Setup(h => h[header.Key])
|
||||
.Returns(header.Value);
|
||||
}
|
||||
|
||||
var requestMock = new Mock<HttpRequest>();
|
||||
requestMock
|
||||
.Setup(r => r.Headers)
|
||||
.Returns(requestHeaderMock.Object);
|
||||
|
||||
var connectionInfoMock = new Mock<ConnectionInfo>();
|
||||
connectionInfoMock
|
||||
.Setup(ci => ci.LocalIpAddress)
|
||||
.Returns(IPAddress.Loopback);
|
||||
connectionInfoMock
|
||||
.Setup(ci => ci.RemoteIpAddress)
|
||||
.Returns(remote ?? IPAddress.Loopback);
|
||||
|
||||
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));
|
||||
|
||||
var configurationMock = new Mock<IConfiguration>();
|
||||
var children = new List<IConfigurationSection>();
|
||||
foreach (string ipAddress in _allowedIpsConfig)
|
||||
{
|
||||
var csm = new Mock<IConfigurationSection>();
|
||||
csm.Setup(cs => cs.Value).Returns(ipAddress);
|
||||
|
||||
children.Add(csm.Object);
|
||||
}
|
||||
|
||||
var configSectionMock = new Mock<IConfigurationSection>();
|
||||
configSectionMock
|
||||
.Setup(cs => cs.GetChildren())
|
||||
.Returns(children);
|
||||
|
||||
configurationMock
|
||||
.Setup(c => c.GetSection(_configKey))
|
||||
.Returns(_configExists ? configSectionMock.Object : null);
|
||||
|
||||
var requestServicesMock = new Mock<IServiceProvider>();
|
||||
requestServicesMock
|
||||
.Setup(s => s.GetService(typeof(IConfiguration)))
|
||||
.Returns(configurationMock.Object);
|
||||
|
||||
var contextMock = new Mock<HttpContext>();
|
||||
contextMock
|
||||
.Setup(c => c.Request)
|
||||
.Returns(requestMock.Object);
|
||||
contextMock
|
||||
.Setup(c => c.RequestServices)
|
||||
.Returns(requestServicesMock.Object);
|
||||
contextMock
|
||||
.Setup(c => c.Items)
|
||||
.Returns(itemsMock.Object);
|
||||
contextMock
|
||||
.Setup(c => c.Connection)
|
||||
.Returns(connectionInfoMock.Object);
|
||||
|
||||
var routeDataMock = new Mock<RouteData>();
|
||||
var actionDescriptorMock = new Mock<ActionDescriptor>();
|
||||
|
||||
return new ActionExecutingContext(new ActionContext
|
||||
{
|
||||
HttpContext = contextMock.Object,
|
||||
RouteData = routeDataMock.Object,
|
||||
ActionDescriptor = actionDescriptorMock.Object,
|
||||
}, new List<IFilterMetadata>(), new Dictionary<string, object>(), null);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,338 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Net;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.Mvc.Abstractions;
|
||||
using Microsoft.AspNetCore.Mvc.Filters;
|
||||
using Microsoft.AspNetCore.Routing;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Moq;
|
||||
|
||||
namespace AMWD.Common.AspNetCore.Tests.Attributes
|
||||
{
|
||||
[TestClass]
|
||||
public class IPBlockListAttributeTest
|
||||
{
|
||||
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 = [];
|
||||
_itemsCallback = [];
|
||||
_configKey = null;
|
||||
_configExists = false;
|
||||
_restrictedIpsConfig = [];
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ShouldAllowOnNoConfiguration()
|
||||
{
|
||||
// arrange
|
||||
var remote = IPAddress.Parse("192.168.178.1");
|
||||
var attribute = new IPBlockListAttribute();
|
||||
var context = GetContext(remote);
|
||||
|
||||
// act
|
||||
attribute.OnActionExecuting(context);
|
||||
|
||||
// assert
|
||||
Assert.IsNull(context.Result);
|
||||
Assert.AreEqual(1, _itemsCallback.Count);
|
||||
Assert.AreEqual(remote, _itemsCallback["RemoteAddress"]);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ShouldAllowOnWrongConfiguration()
|
||||
{
|
||||
// arrange
|
||||
var remote = IPAddress.Parse("192.168.178.1");
|
||||
var attribute = new IPBlockListAttribute
|
||||
{
|
||||
BlockedIpAddresses = "192.168.178:1"
|
||||
};
|
||||
var context = GetContext(remote);
|
||||
|
||||
// act
|
||||
attribute.OnActionExecuting(context);
|
||||
|
||||
// assert
|
||||
Assert.IsNull(context.Result);
|
||||
Assert.AreEqual(1, _itemsCallback.Count);
|
||||
Assert.AreEqual(remote, _itemsCallback["RemoteAddress"]);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ShouldAllowLocalAccess()
|
||||
{
|
||||
// arrange
|
||||
var attribute = new IPBlockListAttribute
|
||||
{
|
||||
BlockLocalAccess = false,
|
||||
BlockedIpAddresses = "127.0.0.0/8"
|
||||
};
|
||||
var context = GetContext();
|
||||
|
||||
// act
|
||||
attribute.OnActionExecuting(context);
|
||||
|
||||
// assert
|
||||
Assert.IsNull(context.Result);
|
||||
Assert.AreEqual(1, _itemsCallback.Count);
|
||||
Assert.AreEqual(IPAddress.Loopback, _itemsCallback["RemoteAddress"]);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ShouldBlockLocalAccess()
|
||||
{
|
||||
// arrange
|
||||
var attribute = new IPBlockListAttribute
|
||||
{
|
||||
BlockLocalAccess = true,
|
||||
BlockedIpAddresses = ",127.0.0.0/8"
|
||||
};
|
||||
var context = GetContext();
|
||||
|
||||
// act
|
||||
attribute.OnActionExecuting(context);
|
||||
|
||||
// assert
|
||||
Assert.IsNotNull(context.Result);
|
||||
Assert.IsTrue(context.Result is StatusCodeResult);
|
||||
Assert.AreEqual(403, ((StatusCodeResult)context.Result).StatusCode);
|
||||
|
||||
Assert.AreEqual(1, _itemsCallback.Count);
|
||||
Assert.AreEqual(IPAddress.Loopback, _itemsCallback["RemoteAddress"]);
|
||||
}
|
||||
|
||||
[DataTestMethod]
|
||||
[DataRow("192.168.178.10")]
|
||||
[DataRow("192.168.178.20")]
|
||||
public void ShouldBlockSpecificAddress(string address)
|
||||
{
|
||||
// arrange
|
||||
var remote = IPAddress.Parse(address);
|
||||
var attribute = new IPBlockListAttribute
|
||||
{
|
||||
BlockLocalAccess = true,
|
||||
BlockedIpAddresses = "127.0.0.0/8,192.168.178.10"
|
||||
};
|
||||
var context = GetContext(remote);
|
||||
|
||||
// act
|
||||
attribute.OnActionExecuting(context);
|
||||
|
||||
// assert
|
||||
if (address == "192.168.178.10")
|
||||
{
|
||||
Assert.IsNotNull(context.Result);
|
||||
Assert.IsTrue(context.Result is StatusCodeResult);
|
||||
Assert.AreEqual(403, ((StatusCodeResult)context.Result).StatusCode);
|
||||
}
|
||||
else
|
||||
{
|
||||
Assert.IsNull(context.Result);
|
||||
}
|
||||
|
||||
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 IPBlockListAttribute
|
||||
{
|
||||
BlockLocalAccess = false,
|
||||
ConfigurationKey = _configKey
|
||||
};
|
||||
var context = GetContext();
|
||||
|
||||
// act
|
||||
attribute.OnActionExecuting(context);
|
||||
|
||||
// assert
|
||||
Assert.IsNull(context.Result);
|
||||
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 IPBlockListAttribute
|
||||
{
|
||||
BlockLocalAccess = true,
|
||||
ConfigurationKey = _configKey
|
||||
};
|
||||
var context = GetContext();
|
||||
|
||||
// act
|
||||
attribute.OnActionExecuting(context);
|
||||
|
||||
// assert
|
||||
Assert.IsNotNull(context.Result);
|
||||
Assert.IsTrue(context.Result is StatusCodeResult);
|
||||
Assert.AreEqual(403, ((StatusCodeResult)context.Result).StatusCode);
|
||||
|
||||
Assert.AreEqual(1, _itemsCallback.Count);
|
||||
Assert.AreEqual(IPAddress.Loopback, _itemsCallback["RemoteAddress"]);
|
||||
}
|
||||
|
||||
[DataTestMethod]
|
||||
[DataRow("192.168.178.10")]
|
||||
[DataRow("192.168.178.20")]
|
||||
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 IPBlockListAttribute
|
||||
{
|
||||
BlockLocalAccess = true,
|
||||
ConfigurationKey = _configKey
|
||||
};
|
||||
var remote = IPAddress.Parse(address);
|
||||
var context = GetContext(remote);
|
||||
|
||||
// act
|
||||
attribute.OnActionExecuting(context);
|
||||
|
||||
// assert
|
||||
if (address == "192.168.178.10")
|
||||
{
|
||||
Assert.IsNotNull(context.Result);
|
||||
Assert.IsTrue(context.Result is StatusCodeResult);
|
||||
Assert.AreEqual(403, ((StatusCodeResult)context.Result).StatusCode);
|
||||
}
|
||||
else
|
||||
{
|
||||
Assert.IsNull(context.Result);
|
||||
}
|
||||
|
||||
Assert.AreEqual(1, _itemsCallback.Count);
|
||||
Assert.AreEqual(remote, _itemsCallback["RemoteAddress"]);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ShouldAllowOnMissingConfiguration()
|
||||
{
|
||||
// arrange
|
||||
_configKey = "Black:List";
|
||||
_configExists = false;
|
||||
var attribute = new IPBlockListAttribute
|
||||
{
|
||||
BlockLocalAccess = true,
|
||||
ConfigurationKey = _configKey
|
||||
};
|
||||
var context = GetContext();
|
||||
|
||||
// act
|
||||
attribute.OnActionExecuting(context);
|
||||
|
||||
// assert
|
||||
Assert.IsNull(context.Result);
|
||||
|
||||
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)
|
||||
{
|
||||
requestHeaderMock
|
||||
.Setup(h => h.ContainsKey(header.Key))
|
||||
.Returns(true);
|
||||
requestHeaderMock
|
||||
.Setup(h => h[header.Key])
|
||||
.Returns(header.Value);
|
||||
}
|
||||
|
||||
var requestMock = new Mock<HttpRequest>();
|
||||
requestMock
|
||||
.Setup(r => r.Headers)
|
||||
.Returns(requestHeaderMock.Object);
|
||||
|
||||
var connectionInfoMock = new Mock<ConnectionInfo>();
|
||||
connectionInfoMock
|
||||
.Setup(ci => ci.LocalIpAddress)
|
||||
.Returns(IPAddress.Loopback);
|
||||
connectionInfoMock
|
||||
.Setup(ci => ci.RemoteIpAddress)
|
||||
.Returns(remote ?? IPAddress.Loopback);
|
||||
|
||||
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));
|
||||
|
||||
var configurationMock = new Mock<IConfiguration>();
|
||||
var children = new List<IConfigurationSection>();
|
||||
foreach (string ipAddress in _restrictedIpsConfig)
|
||||
{
|
||||
var csm = new Mock<IConfigurationSection>();
|
||||
csm.Setup(cs => cs.Value).Returns(ipAddress);
|
||||
|
||||
children.Add(csm.Object);
|
||||
}
|
||||
|
||||
var configSectionMock = new Mock<IConfigurationSection>();
|
||||
configSectionMock
|
||||
.Setup(cs => cs.GetChildren())
|
||||
.Returns(children);
|
||||
|
||||
configurationMock
|
||||
.Setup(c => c.GetSection(_configKey))
|
||||
.Returns(_configExists ? configSectionMock.Object : null);
|
||||
|
||||
var requestServicesMock = new Mock<IServiceProvider>();
|
||||
requestServicesMock
|
||||
.Setup(s => s.GetService(typeof(IConfiguration)))
|
||||
.Returns(configurationMock.Object);
|
||||
|
||||
var contextMock = new Mock<HttpContext>();
|
||||
contextMock
|
||||
.Setup(c => c.Request)
|
||||
.Returns(requestMock.Object);
|
||||
contextMock
|
||||
.Setup(c => c.RequestServices)
|
||||
.Returns(requestServicesMock.Object);
|
||||
contextMock
|
||||
.Setup(c => c.Items)
|
||||
.Returns(itemsMock.Object);
|
||||
contextMock
|
||||
.Setup(c => c.Connection)
|
||||
.Returns(connectionInfoMock.Object);
|
||||
|
||||
var routeDataMock = new Mock<RouteData>();
|
||||
var actionDescriptorMock = new Mock<ActionDescriptor>();
|
||||
|
||||
return new ActionExecutingContext(new ActionContext
|
||||
{
|
||||
HttpContext = contextMock.Object,
|
||||
RouteData = routeDataMock.Object,
|
||||
ActionDescriptor = actionDescriptorMock.Object,
|
||||
}, new List<IFilterMetadata>(), new Dictionary<string, object>(), null);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,477 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Net;
|
||||
using Microsoft.AspNetCore.Antiforgery;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Moq;
|
||||
|
||||
namespace AMWD.Common.AspNetCore.Tests.Extensions
|
||||
{
|
||||
[TestClass]
|
||||
public class HttpContextExtensionsTest
|
||||
{
|
||||
private Mock<ISession> _sessionMock;
|
||||
|
||||
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 IPAddress _remote;
|
||||
|
||||
[TestInitialize]
|
||||
public void InitializeTests()
|
||||
{
|
||||
_tokenFormName = null;
|
||||
_tokenHeaderName = null;
|
||||
_tokenValue = null;
|
||||
|
||||
_requestHeaders = [];
|
||||
_requestQueries = [];
|
||||
_items = [];
|
||||
|
||||
_remote = IPAddress.Loopback;
|
||||
}
|
||||
|
||||
#region Antiforgery
|
||||
|
||||
[TestMethod]
|
||||
public void ShouldReturnAntiforgery()
|
||||
{
|
||||
// arrange
|
||||
_tokenFormName = "af-token";
|
||||
_tokenHeaderName = "af-header";
|
||||
_tokenValue = "security_first";
|
||||
|
||||
var context = GetContext();
|
||||
|
||||
// act
|
||||
var (formName, headerName, value) = context.GetAntiforgeryToken();
|
||||
|
||||
// assert
|
||||
Assert.AreEqual(_tokenFormName, formName);
|
||||
Assert.AreEqual(_tokenHeaderName, headerName);
|
||||
Assert.AreEqual(_tokenValue, value);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ShouldReturnAntiforgeryNullService()
|
||||
{
|
||||
// arrange
|
||||
_tokenFormName = "af-token";
|
||||
_tokenHeaderName = "af-header";
|
||||
_tokenValue = "security_first";
|
||||
|
||||
var context = GetContext(hasAntiforgery: false);
|
||||
|
||||
// act
|
||||
var (formName, headerName, value) = context.GetAntiforgeryToken();
|
||||
|
||||
// assert
|
||||
Assert.IsNull(formName);
|
||||
Assert.IsNull(headerName);
|
||||
Assert.IsNull(value);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ShouldReturnAntiforgeryNullToken()
|
||||
{
|
||||
// arrange
|
||||
var context = GetContext();
|
||||
|
||||
// act
|
||||
var (formName, headerName, value) = context.GetAntiforgeryToken();
|
||||
|
||||
// assert
|
||||
Assert.IsNull(formName);
|
||||
Assert.IsNull(headerName);
|
||||
Assert.IsNull(value);
|
||||
}
|
||||
|
||||
#endregion Antiforgery
|
||||
|
||||
#region RemoteAddres
|
||||
|
||||
[TestMethod]
|
||||
public void ShouldReturnRemoteAddress()
|
||||
{
|
||||
// arrange
|
||||
_remote = IPAddress.Parse("1.2.3.4");
|
||||
|
||||
var context = GetContext();
|
||||
|
||||
// act
|
||||
var result = context.GetRemoteIpAddress();
|
||||
|
||||
// assert
|
||||
Assert.AreEqual(_remote, result);
|
||||
}
|
||||
|
||||
[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");
|
||||
var header = IPAddress.Parse("5.6.7.8");
|
||||
_requestHeaders.Add(headerName, header.ToString());
|
||||
|
||||
var context = GetContext();
|
||||
|
||||
// act
|
||||
var result = context.GetRemoteIpAddress();
|
||||
|
||||
// assert
|
||||
Assert.AreNotEqual(_remote, result);
|
||||
Assert.AreEqual(header, result);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ShouldReturnCustomHeader()
|
||||
{
|
||||
// arrange
|
||||
_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("X-Forwarded-For", _remote.ToString());
|
||||
|
||||
var context = GetContext();
|
||||
|
||||
// act
|
||||
var result = context.GetRemoteIpAddress(ipHeaderName: headerName);
|
||||
|
||||
// assert
|
||||
Assert.AreNotEqual(_remote, result);
|
||||
Assert.AreEqual(headerIp, result);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ShouldReturnAddressInvalidHeader()
|
||||
{
|
||||
// arrange
|
||||
_remote = IPAddress.Parse("1.2.3.4");
|
||||
_requestHeaders.Add("X-Forwarded-For", "1.2.3:4");
|
||||
|
||||
var context = GetContext();
|
||||
|
||||
// act
|
||||
var result = context.GetRemoteIpAddress();
|
||||
|
||||
// assert
|
||||
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
|
||||
|
||||
#region Local Request
|
||||
|
||||
[TestMethod]
|
||||
public void ShouldReturnTrueOnLocal()
|
||||
{
|
||||
// arrange
|
||||
_remote = IPAddress.Loopback;
|
||||
|
||||
var context = GetContext();
|
||||
|
||||
// act
|
||||
bool result = context.IsLocalRequest();
|
||||
|
||||
// assert
|
||||
Assert.IsTrue(result);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ShouldReturnFalseOnRemote()
|
||||
{
|
||||
// arrange
|
||||
_remote = IPAddress.Parse("1.2.3.4");
|
||||
|
||||
var context = GetContext();
|
||||
|
||||
// act
|
||||
bool result = context.IsLocalRequest();
|
||||
|
||||
// assert
|
||||
Assert.IsFalse(result);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ShouldReturnTrueOnDefaultHeader()
|
||||
{
|
||||
// arrange
|
||||
_remote = IPAddress.Parse("1.2.3.4");
|
||||
var headerIp = IPAddress.Loopback;
|
||||
_requestHeaders.Add("X-Forwarded-For", headerIp.ToString());
|
||||
|
||||
var context = GetContext();
|
||||
|
||||
// act
|
||||
bool result = context.IsLocalRequest();
|
||||
|
||||
// assert
|
||||
Assert.IsTrue(result);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ShouldReturnTrueOnCustomHeader()
|
||||
{
|
||||
// arrange
|
||||
_remote = IPAddress.Parse("1.2.3.4");
|
||||
string headerName = "FooBar";
|
||||
var headerIp = IPAddress.Loopback;
|
||||
_requestHeaders.Add(headerName, headerIp.ToString());
|
||||
|
||||
var context = GetContext();
|
||||
|
||||
// act
|
||||
bool result = context.IsLocalRequest(ipHeaderName: headerName);
|
||||
|
||||
// assert
|
||||
Assert.IsTrue(result);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ShouldReturnFalseOnDefaultHeader()
|
||||
{
|
||||
// arrange
|
||||
var headerIp = IPAddress.Parse("1.2.3.4");
|
||||
_requestHeaders.Add("X-Forwarded-For", headerIp.ToString());
|
||||
|
||||
var context = GetContext();
|
||||
|
||||
// act
|
||||
bool result = context.IsLocalRequest();
|
||||
|
||||
// assert
|
||||
Assert.IsFalse(result);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ShouldReturnFalseOnCustomHeader()
|
||||
{
|
||||
// arrange
|
||||
string headerName = "FooBar";
|
||||
var headerIp = IPAddress.Parse("1.2.3.4");
|
||||
_requestHeaders.Add(headerName, headerIp.ToString());
|
||||
|
||||
var context = GetContext();
|
||||
|
||||
// act
|
||||
bool result = context.IsLocalRequest(ipHeaderName: headerName);
|
||||
|
||||
// assert
|
||||
Assert.IsFalse(result);
|
||||
}
|
||||
|
||||
#endregion Local Request
|
||||
|
||||
#region ReturnUrl
|
||||
|
||||
[TestMethod]
|
||||
public void ShouldReturnNull()
|
||||
{
|
||||
// arrange
|
||||
var context = GetContext();
|
||||
|
||||
// act
|
||||
string result = context.GetReturnUrl();
|
||||
|
||||
// assert
|
||||
Assert.IsNull(result);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ShouldReturnOriginalRequest()
|
||||
{
|
||||
// arrange
|
||||
string request = "abc";
|
||||
string query = "def";
|
||||
|
||||
_items.Add("OriginalRequest", request);
|
||||
_requestQueries.Add("ReturnUrl", query);
|
||||
|
||||
var context = GetContext();
|
||||
|
||||
// act
|
||||
string result = context.GetReturnUrl();
|
||||
|
||||
// assert
|
||||
Assert.AreEqual(request, result);
|
||||
Assert.AreNotEqual(query, result);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ShouldReturnUrl()
|
||||
{
|
||||
// arrange
|
||||
string query = "def";
|
||||
_requestQueries.Add("ReturnUrl", query);
|
||||
|
||||
var context = GetContext();
|
||||
|
||||
// act
|
||||
string result = context.GetReturnUrl();
|
||||
|
||||
// assert
|
||||
Assert.AreEqual(query, result);
|
||||
}
|
||||
|
||||
#endregion ReturnUrl
|
||||
|
||||
#region Session
|
||||
|
||||
[TestMethod]
|
||||
public void ShouldClearSession()
|
||||
{
|
||||
// arrange
|
||||
var context = GetContext();
|
||||
|
||||
// act
|
||||
context.ClearSession();
|
||||
|
||||
// assert
|
||||
_sessionMock.Verify(s => s.Clear(), Times.Once);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ShouldSkipWhenNoSession()
|
||||
{
|
||||
// arrange
|
||||
var context = GetContext(hasSession: false);
|
||||
|
||||
// act
|
||||
context.ClearSession();
|
||||
|
||||
// assert
|
||||
_sessionMock.Verify(s => s.Clear(), Times.Never);
|
||||
}
|
||||
|
||||
#endregion Session
|
||||
|
||||
private HttpContext GetContext(bool hasAntiforgery = true, bool hasSession = true)
|
||||
{
|
||||
// Request
|
||||
var requestHeaderMock = new Mock<IHeaderDictionary>();
|
||||
foreach (var header in _requestHeaders)
|
||||
{
|
||||
requestHeaderMock
|
||||
.Setup(h => h.ContainsKey(header.Key))
|
||||
.Returns(true);
|
||||
requestHeaderMock
|
||||
.Setup(h => h[header.Key])
|
||||
.Returns(header.Value);
|
||||
}
|
||||
|
||||
var requestQueryMock = new Mock<IQueryCollection>();
|
||||
foreach (var query in _requestQueries)
|
||||
{
|
||||
requestQueryMock
|
||||
.Setup(h => h.ContainsKey(query.Key))
|
||||
.Returns(true);
|
||||
requestQueryMock
|
||||
.Setup(h => h[query.Key])
|
||||
.Returns(query.Value);
|
||||
}
|
||||
|
||||
var requestMock = new Mock<HttpRequest>();
|
||||
requestMock
|
||||
.Setup(r => r.Headers)
|
||||
.Returns(requestHeaderMock.Object);
|
||||
requestMock
|
||||
.Setup(r => r.Query)
|
||||
.Returns(requestQueryMock.Object);
|
||||
|
||||
// Request Services
|
||||
var requestServicesMock = new Mock<IServiceProvider>();
|
||||
if (hasAntiforgery)
|
||||
{
|
||||
var antiforgeryMock = new Mock<IAntiforgery>();
|
||||
antiforgeryMock
|
||||
.Setup(af => af.GetAndStoreTokens(It.IsAny<HttpContext>()))
|
||||
.Returns(() => string.IsNullOrWhiteSpace(_tokenValue) ? null : new AntiforgeryTokenSet(_tokenValue, _tokenValue, _tokenFormName, _tokenHeaderName));
|
||||
|
||||
requestServicesMock
|
||||
.Setup(rs => rs.GetService(typeof(IAntiforgery)))
|
||||
.Returns(antiforgeryMock.Object);
|
||||
}
|
||||
|
||||
// Connection
|
||||
var connectionInfoMock = new Mock<ConnectionInfo>();
|
||||
connectionInfoMock
|
||||
.Setup(ci => ci.LocalIpAddress)
|
||||
.Returns(IPAddress.Loopback);
|
||||
connectionInfoMock
|
||||
.Setup(ci => ci.RemoteIpAddress)
|
||||
.Returns(_remote);
|
||||
|
||||
// Session
|
||||
_sessionMock = new Mock<ISession>();
|
||||
|
||||
var contextMock = new Mock<HttpContext>();
|
||||
contextMock
|
||||
.Setup(c => c.Request)
|
||||
.Returns(requestMock.Object);
|
||||
contextMock
|
||||
.Setup(c => c.RequestServices)
|
||||
.Returns(requestServicesMock.Object);
|
||||
contextMock
|
||||
.Setup(c => c.Connection)
|
||||
.Returns(connectionInfoMock.Object);
|
||||
contextMock
|
||||
.Setup(c => c.Items)
|
||||
.Returns(_items);
|
||||
if (hasSession)
|
||||
{
|
||||
contextMock
|
||||
.Setup(c => c.Session)
|
||||
.Returns(_sessionMock.Object);
|
||||
}
|
||||
|
||||
return contextMock.Object;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,95 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using Microsoft.AspNetCore.Mvc.ModelBinding;
|
||||
|
||||
namespace AMWD.Common.AspNetCore.Tests.Extensions
|
||||
{
|
||||
[TestClass]
|
||||
public class ModelStateDictionaryExtensionsTest
|
||||
{
|
||||
private TestModel _testModel;
|
||||
|
||||
[TestInitialize]
|
||||
public void InitializeTests()
|
||||
{
|
||||
_testModel = new TestModel
|
||||
{
|
||||
ValueA = "A",
|
||||
ValueB = "B",
|
||||
SubModel = new TestSubModel
|
||||
{
|
||||
SubValueA = "a",
|
||||
SubValueB = "b"
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ShouldAddNormalModelError()
|
||||
{
|
||||
// arrange
|
||||
var modelState = new ModelStateDictionary();
|
||||
|
||||
// act
|
||||
modelState.AddModelError(_testModel, m => m.ValueA, "ShitHappens");
|
||||
|
||||
// assert
|
||||
Assert.AreEqual(1, modelState.Count);
|
||||
Assert.AreEqual("ValueA", modelState.Keys.First());
|
||||
Assert.AreEqual("ShitHappens", modelState.Values.First().Errors.First().ErrorMessage);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ShouldAddExtendedModelError()
|
||||
{
|
||||
// arrange
|
||||
var modelState = new ModelStateDictionary();
|
||||
|
||||
// act
|
||||
modelState.AddModelError(_testModel, m => m.SubModel.SubValueB, "ShitHappens");
|
||||
|
||||
// assert
|
||||
Assert.AreEqual(1, modelState.Count);
|
||||
Assert.AreEqual("SubModel.SubValueB", modelState.Keys.First());
|
||||
Assert.AreEqual("ShitHappens", modelState.Values.First().Errors.First().ErrorMessage);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[ExpectedException(typeof(ArgumentNullException))]
|
||||
public void ShouldThrowArgumentNull()
|
||||
{
|
||||
// arrange
|
||||
ModelStateDictionary modelState = null;
|
||||
|
||||
// act
|
||||
modelState.AddModelError(_testModel, m => m.SubModel.SubValueB, "ShitHappens");
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[ExpectedException(typeof(InvalidOperationException))]
|
||||
public void ShouldThrowInvalidOperation()
|
||||
{
|
||||
// arrange
|
||||
var modelState = new ModelStateDictionary();
|
||||
|
||||
// act
|
||||
modelState.AddModelError(_testModel, m => m, "ShitHappens");
|
||||
}
|
||||
|
||||
internal class TestModel
|
||||
{
|
||||
public string ValueA { get; set; }
|
||||
|
||||
public string ValueB { get; set; }
|
||||
|
||||
public TestSubModel SubModel { get; set; }
|
||||
}
|
||||
|
||||
internal class TestSubModel
|
||||
{
|
||||
public string SubValueA { get; set; }
|
||||
|
||||
public string SubValueB { get; set; }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,157 @@
|
||||
using System.Text;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Moq;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace AMWD.Common.AspNetCore.Tests.Extensions
|
||||
{
|
||||
[TestClass]
|
||||
public class SessionExtensionsTest
|
||||
{
|
||||
private Mock<ISession> _sessionMock;
|
||||
|
||||
private string _sessionKey;
|
||||
private byte[] _sessionValue;
|
||||
|
||||
private TestModel _model;
|
||||
|
||||
internal class TestModel
|
||||
{
|
||||
public string ValueA { get; set; }
|
||||
|
||||
public string ValueB { get; set; }
|
||||
}
|
||||
|
||||
[TestInitialize]
|
||||
public void InitializeTests()
|
||||
{
|
||||
_sessionKey = null;
|
||||
_sessionValue = null;
|
||||
|
||||
_model = new TestModel
|
||||
{
|
||||
ValueA = "A",
|
||||
ValueB = "B"
|
||||
};
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ShouldCheckKeyExists()
|
||||
{
|
||||
// arrange
|
||||
_sessionKey = "exists";
|
||||
var session = GetSession();
|
||||
|
||||
// act
|
||||
bool result1 = session.HasKey("exists");
|
||||
bool result2 = session.HasKey("somewhere");
|
||||
|
||||
// assert
|
||||
Assert.IsTrue(result1);
|
||||
Assert.IsFalse(result2);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ShouldGetValue()
|
||||
{
|
||||
// arrange
|
||||
_sessionKey = "test";
|
||||
_sessionValue = Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(_model));
|
||||
var session = GetSession();
|
||||
|
||||
// act
|
||||
var result = session.GetValue<TestModel>(_sessionKey);
|
||||
|
||||
// assert
|
||||
Assert.IsNotNull(result);
|
||||
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));
|
||||
var session = GetSession();
|
||||
|
||||
// act
|
||||
var result = session.GetValue<TestModel>("bar");
|
||||
|
||||
// assert
|
||||
Assert.IsNull(result);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ShouldGetValueWithFallback()
|
||||
{
|
||||
// arrange
|
||||
_sessionKey = "test";
|
||||
_sessionValue = Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(_model));
|
||||
var session = GetSession();
|
||||
|
||||
// act
|
||||
var result = session.GetValue(_sessionKey, new TestModel());
|
||||
|
||||
// assert
|
||||
Assert.IsNotNull(result);
|
||||
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));
|
||||
var session = GetSession();
|
||||
|
||||
// act
|
||||
var result = session.GetValue("bar", new TestModel());
|
||||
|
||||
// assert
|
||||
Assert.IsNotNull(result);
|
||||
Assert.AreEqual(null, result.ValueA);
|
||||
Assert.AreEqual(null, result.ValueB);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ShouldSetValue()
|
||||
{
|
||||
// arrange
|
||||
string key = "foobar";
|
||||
var session = GetSession();
|
||||
|
||||
// act
|
||||
session.SetValue(key, _model);
|
||||
|
||||
// arrange
|
||||
Assert.AreEqual(key, _sessionKey);
|
||||
Assert.AreEqual(JsonConvert.SerializeObject(_model), Encoding.UTF8.GetString(_sessionValue));
|
||||
}
|
||||
|
||||
private ISession GetSession()
|
||||
{
|
||||
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
|
||||
.Setup(s => s.Set(It.IsAny<string>(), It.IsAny<byte[]>()))
|
||||
.Callback<string, byte[]>((key, value) =>
|
||||
{
|
||||
_sessionKey = key;
|
||||
_sessionValue = value;
|
||||
});
|
||||
_sessionMock
|
||||
.Setup(s => s.Keys)
|
||||
.Returns(keys);
|
||||
|
||||
return _sessionMock.Object;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,226 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Security.Claims;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using AMWD.Common.AspNetCore.Security.BasicAuthentication;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.Extensions.Primitives;
|
||||
using Microsoft.Net.Http.Headers;
|
||||
using Moq;
|
||||
|
||||
namespace AMWD.Common.AspNetCore.Tests.Security.BasicAuthentication
|
||||
{
|
||||
[TestClass]
|
||||
public class BasicAuthenticationMiddlewareTest
|
||||
{
|
||||
private Dictionary<string, string> _requestHeaders;
|
||||
|
||||
private Dictionary<string, string> _responseHeadersCallback;
|
||||
private int _responseStatusCodeCallback;
|
||||
|
||||
private string _validatorRealm;
|
||||
private ClaimsPrincipal _validatorResponse;
|
||||
private List<(string username, string password, IPAddress ipAddr)> _validatorCallback;
|
||||
|
||||
[TestInitialize]
|
||||
public void InitializeTests()
|
||||
{
|
||||
_requestHeaders = [];
|
||||
|
||||
_responseHeadersCallback = [];
|
||||
_responseStatusCodeCallback = 0;
|
||||
|
||||
_validatorRealm = null;
|
||||
_validatorResponse = null;
|
||||
_validatorCallback = [];
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public async Task ShouldAllowAccess()
|
||||
{
|
||||
// arrange
|
||||
string username = "user";
|
||||
string password = "pass:word";
|
||||
|
||||
_requestHeaders.Add("Authorization", $"Basic {Convert.ToBase64String(Encoding.UTF8.GetBytes($"{username}:{password}"))}");
|
||||
_validatorResponse = new ClaimsPrincipal();
|
||||
|
||||
var middleware = GetMiddleware();
|
||||
var context = GetContext();
|
||||
|
||||
// act
|
||||
await middleware.InvokeAsync(context);
|
||||
|
||||
// assert
|
||||
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);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public async Task ShouldDenyMissingHeader()
|
||||
{
|
||||
// arrange
|
||||
var middleware = GetMiddleware();
|
||||
var context = GetContext();
|
||||
|
||||
// act
|
||||
await middleware.InvokeAsync(context);
|
||||
|
||||
// assert
|
||||
Assert.AreEqual(401, _responseStatusCodeCallback);
|
||||
|
||||
Assert.AreEqual(0, _validatorCallback.Count);
|
||||
|
||||
Assert.AreEqual(1, _responseHeadersCallback.Count);
|
||||
Assert.AreEqual("WWW-Authenticate", _responseHeadersCallback.Keys.First());
|
||||
Assert.AreEqual("Basic", _responseHeadersCallback.Values.First());
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public async Task ShouldDenyNoResult()
|
||||
{
|
||||
// arrange
|
||||
string username = "user";
|
||||
string password = "pw";
|
||||
|
||||
_validatorRealm = "TEST";
|
||||
var remote = IPAddress.Parse("1.2.3.4");
|
||||
|
||||
_requestHeaders.Add("Authorization", $"Basic {Convert.ToBase64String(Encoding.UTF8.GetBytes($"{username}:{password}"))}");
|
||||
|
||||
var middleware = GetMiddleware();
|
||||
var context = GetContext(remote);
|
||||
|
||||
// act
|
||||
await middleware.InvokeAsync(context);
|
||||
|
||||
// assert
|
||||
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, _validatorCallback.Count);
|
||||
Assert.AreEqual(username, _validatorCallback.First().username);
|
||||
Assert.AreEqual(password, _validatorCallback.First().password);
|
||||
Assert.AreEqual(remote, _validatorCallback.First().ipAddr);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public async Task ShouldBreakOnException()
|
||||
{
|
||||
// arrange
|
||||
string username = "user";
|
||||
|
||||
_requestHeaders.Add("Authorization", $"Basic {Convert.ToBase64String(Encoding.UTF8.GetBytes($"{username}"))}");
|
||||
|
||||
var middleware = GetMiddleware();
|
||||
var context = GetContext();
|
||||
|
||||
// act
|
||||
await middleware.InvokeAsync(context);
|
||||
|
||||
// assert
|
||||
Assert.AreEqual(500, _responseStatusCodeCallback);
|
||||
}
|
||||
|
||||
private BasicAuthenticationMiddleware GetMiddleware()
|
||||
{
|
||||
var nextMock = new Mock<RequestDelegate>();
|
||||
var validatorMock = new Mock<IBasicAuthenticationValidator>();
|
||||
validatorMock
|
||||
.Setup(v => v.Realm)
|
||||
.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);
|
||||
|
||||
return new BasicAuthenticationMiddleware(nextMock.Object, validatorMock.Object);
|
||||
}
|
||||
|
||||
private HttpContext GetContext(IPAddress remote = null)
|
||||
{
|
||||
// Request
|
||||
var requestHeaderMock = new Mock<IHeaderDictionary>();
|
||||
foreach (var header in _requestHeaders)
|
||||
{
|
||||
var strVal = new StringValues(header.Value);
|
||||
requestHeaderMock
|
||||
.Setup(h => h.ContainsKey(header.Key))
|
||||
.Returns(true);
|
||||
requestHeaderMock
|
||||
.Setup(h => h[header.Key])
|
||||
.Returns(strVal);
|
||||
requestHeaderMock
|
||||
.Setup(h => h.TryGetValue(header.Key, out strVal))
|
||||
.Returns(true);
|
||||
}
|
||||
|
||||
var requestMock = new Mock<HttpRequest>();
|
||||
requestMock
|
||||
.Setup(r => r.Headers)
|
||||
.Returns(requestHeaderMock.Object);
|
||||
|
||||
// Response
|
||||
var responseHeaderMock = new Mock<IHeaderDictionary>();
|
||||
responseHeaderMock
|
||||
.SetupSet(h => h[It.IsAny<string>()] = It.IsAny<StringValues>())
|
||||
.Callback<string, StringValues>((key, value) => _responseHeadersCallback[key] = value);
|
||||
#pragma warning disable CS0618
|
||||
responseHeaderMock
|
||||
.SetupSet(h => h.WWWAuthenticate)
|
||||
.Callback((value) => _responseHeadersCallback[HeaderNames.WWWAuthenticate] = value);
|
||||
#pragma warning restore CS0618
|
||||
|
||||
var responseMock = new Mock<HttpResponse>();
|
||||
responseMock
|
||||
.Setup(r => r.Headers)
|
||||
.Returns(responseHeaderMock.Object);
|
||||
responseMock
|
||||
.SetupSet(r => r.StatusCode = It.IsAny<int>())
|
||||
.Callback<int>((code) => _responseStatusCodeCallback = code);
|
||||
|
||||
// Connection
|
||||
var connectionInfoMock = new Mock<ConnectionInfo>();
|
||||
connectionInfoMock
|
||||
.Setup(ci => ci.LocalIpAddress)
|
||||
.Returns(IPAddress.Loopback);
|
||||
connectionInfoMock
|
||||
.Setup(ci => ci.RemoteIpAddress)
|
||||
.Returns(remote ?? IPAddress.Loopback);
|
||||
|
||||
// Request Services
|
||||
var requestServicesMock = new Mock<IServiceProvider>();
|
||||
|
||||
var contextMock = new Mock<HttpContext>();
|
||||
contextMock
|
||||
.Setup(c => c.Request)
|
||||
.Returns(requestMock.Object);
|
||||
contextMock
|
||||
.Setup(c => c.Response)
|
||||
.Returns(responseMock.Object);
|
||||
contextMock
|
||||
.Setup(c => c.Connection)
|
||||
.Returns(connectionInfoMock.Object);
|
||||
contextMock
|
||||
.Setup(c => c.RequestServices)
|
||||
.Returns(requestServicesMock.Object);
|
||||
contextMock
|
||||
.Setup(c => c.RequestAborted)
|
||||
.Returns(CancellationToken.None);
|
||||
|
||||
return contextMock.Object;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,139 @@
|
||||
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 Moq;
|
||||
|
||||
namespace AMWD.Common.AspNetCore.Tests.Security.PathProtection
|
||||
{
|
||||
[TestClass]
|
||||
public class ProtectedPathMiddlewareTest
|
||||
{
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
153
test/AMWD.Common.AspNetCore.Tests/Utilities/HtmlHelperTest.cs
Normal file
153
test/AMWD.Common.AspNetCore.Tests/Utilities/HtmlHelperTest.cs
Normal file
@@ -0,0 +1,153 @@
|
||||
using System;
|
||||
using AMWD.Common.AspNetCore.Utilities;
|
||||
|
||||
namespace AMWD.Common.AspNetCore.Tests.Utilities
|
||||
{
|
||||
[TestClass]
|
||||
public class HtmlHelperTest
|
||||
{
|
||||
[TestMethod]
|
||||
[ExpectedException(typeof(ArgumentNullException))]
|
||||
public void ShouldThrowErrorOnEmptyColor()
|
||||
{
|
||||
// arrange
|
||||
|
||||
// act
|
||||
HtmlHelper.IsDarkColor("");
|
||||
|
||||
// assert
|
||||
// exception thrown
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[ExpectedException(typeof(NotSupportedException))]
|
||||
public void ShouldThrowErrorOnUnsupportedColor()
|
||||
{
|
||||
// arrange
|
||||
|
||||
// act
|
||||
HtmlHelper.IsDarkColor("hsv(1, 2, 3)");
|
||||
|
||||
// assert
|
||||
// exception thrown
|
||||
}
|
||||
|
||||
[DataTestMethod]
|
||||
[DataRow("rgb(255, 255, 255)")]
|
||||
[DataRow("rgba(255, 255, 255, .5)")]
|
||||
public void ShouldReturnLightColorForWhiteRgb(string white)
|
||||
{
|
||||
// arrange
|
||||
|
||||
// act
|
||||
bool isDark = HtmlHelper.IsDarkColor(white);
|
||||
|
||||
// assert
|
||||
Assert.IsFalse(isDark);
|
||||
}
|
||||
|
||||
[DataTestMethod]
|
||||
[DataRow("#ffFFff")]
|
||||
[DataRow("FFffFF")]
|
||||
public void ShouldReturnLightColorForWhiteFullHex(string white)
|
||||
{
|
||||
// arrange
|
||||
|
||||
// act
|
||||
bool isDark = HtmlHelper.IsDarkColor(white);
|
||||
|
||||
// assert
|
||||
Assert.IsFalse(isDark);
|
||||
}
|
||||
|
||||
[DataTestMethod]
|
||||
[DataRow("#fFf")]
|
||||
[DataRow("FfF")]
|
||||
public void ShouldReturnLightColorForWhiteShortHex(string white)
|
||||
{
|
||||
// arrange
|
||||
|
||||
// act
|
||||
bool isDark = HtmlHelper.IsDarkColor(white);
|
||||
|
||||
// assert
|
||||
Assert.IsFalse(isDark);
|
||||
}
|
||||
|
||||
[DataTestMethod]
|
||||
[DataRow("rgb(0, 0, 0)")]
|
||||
[DataRow("rgba(0, 0, 0, .5)")]
|
||||
public void ShouldReturnDarkColorForBlackRgb(string black)
|
||||
{
|
||||
// arrange
|
||||
|
||||
// act
|
||||
bool isDark = HtmlHelper.IsDarkColor(black);
|
||||
|
||||
// assert
|
||||
Assert.IsTrue(isDark);
|
||||
}
|
||||
|
||||
[DataTestMethod]
|
||||
[DataRow("#000000")]
|
||||
[DataRow("000000")]
|
||||
public void ShouldReturnDarkColorForBlackFullHex(string black)
|
||||
{
|
||||
// arrange
|
||||
|
||||
// act
|
||||
bool isDark = HtmlHelper.IsDarkColor(black);
|
||||
|
||||
// assert
|
||||
Assert.IsTrue(isDark);
|
||||
}
|
||||
|
||||
[DataTestMethod]
|
||||
[DataRow("#000")]
|
||||
[DataRow("000")]
|
||||
public void ShouldReturnDarkColorForBlackShortHex(string black)
|
||||
{
|
||||
// arrange
|
||||
|
||||
// act
|
||||
bool isDark = HtmlHelper.IsDarkColor(black);
|
||||
|
||||
// assert
|
||||
Assert.IsTrue(isDark);
|
||||
}
|
||||
|
||||
[DataTestMethod]
|
||||
[DataRow("rgb(255, 88, 0)")]
|
||||
[DataRow("rgb(0, 218, 0)")]
|
||||
[DataRow("rgb(0, 168, 255)")]
|
||||
[DataRow("rgb(255, 38, 255)")]
|
||||
[DataRow("rgb(128, 128, 128)")]
|
||||
public void ShouldReturnLightColorForBorderColors(string color)
|
||||
{
|
||||
// arrange
|
||||
|
||||
// act
|
||||
bool isDark = HtmlHelper.IsDarkColor(color);
|
||||
|
||||
// assert
|
||||
Assert.IsFalse(isDark);
|
||||
}
|
||||
|
||||
[DataTestMethod]
|
||||
[DataRow("rgb(253, 88, 0)")]
|
||||
[DataRow("rgb(0, 217, 0)")]
|
||||
[DataRow("rgb(0, 168, 253)")]
|
||||
[DataRow("rgb(254, 38, 254)")]
|
||||
[DataRow("rgb(127, 127, 127)")]
|
||||
public void ShouldReturnDarkColorForBorderColors(string color)
|
||||
{
|
||||
// arrange
|
||||
|
||||
// act
|
||||
bool isDark = HtmlHelper.IsDarkColor(color);
|
||||
|
||||
// assert
|
||||
Assert.IsTrue(isDark);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,157 @@
|
||||
using System.Reflection;
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
|
||||
namespace AMWD.Common.AspNetCore.Tests.Utilities
|
||||
{
|
||||
[TestClass]
|
||||
public class PasswordHelperTest
|
||||
{
|
||||
[TestMethod]
|
||||
public void ShouldReturnNullHashWhenNullProvided()
|
||||
{
|
||||
// arrange
|
||||
string password = null;
|
||||
|
||||
// act
|
||||
string hash = PasswordHelper.HashPassword(password);
|
||||
|
||||
// assert
|
||||
Assert.IsNull(hash);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ShouldReturnEmptyHashWhenSpacesProvided()
|
||||
{
|
||||
// arrange
|
||||
string password = " ";
|
||||
|
||||
// act
|
||||
string hash = PasswordHelper.HashPassword(password);
|
||||
|
||||
// assert
|
||||
Assert.AreEqual("", hash);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ShouldReturnHashWhenTextProvided()
|
||||
{
|
||||
// arrange
|
||||
string password = "password";
|
||||
|
||||
// act
|
||||
string hash = PasswordHelper.HashPassword(password);
|
||||
|
||||
// assert
|
||||
Assert.IsTrue(!string.IsNullOrWhiteSpace(hash));
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ShouldReturnFalseOnNullPassword()
|
||||
{
|
||||
// arrange
|
||||
string password = null;
|
||||
string hash = PasswordHelper.HashPassword(password);
|
||||
|
||||
// act
|
||||
bool isValid = PasswordHelper.VerifyPassword(password, hash, out bool rehashNeeded);
|
||||
|
||||
// assert
|
||||
Assert.IsFalse(isValid);
|
||||
Assert.IsFalse(rehashNeeded);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ShouldReturnFalseOnEmptyPassword()
|
||||
{
|
||||
// arrange
|
||||
string password = " ";
|
||||
string hash = PasswordHelper.HashPassword(password);
|
||||
|
||||
// act
|
||||
bool isValid = PasswordHelper.VerifyPassword(password, hash, out bool rehashNeeded);
|
||||
|
||||
// assert
|
||||
Assert.IsFalse(isValid);
|
||||
Assert.IsFalse(rehashNeeded);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ShouldReturnFalseOnNullHash()
|
||||
{
|
||||
// arrange
|
||||
string password = "password";
|
||||
string hash = null;
|
||||
|
||||
// act
|
||||
bool isValid = PasswordHelper.VerifyPassword(password, hash, out bool rehashNeeded);
|
||||
|
||||
// assert
|
||||
Assert.IsFalse(isValid);
|
||||
Assert.IsFalse(rehashNeeded);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ShouldReturnFalseOnEmptyHash()
|
||||
{
|
||||
// arrange
|
||||
string password = "password";
|
||||
string hash = "";
|
||||
|
||||
// act
|
||||
bool isValid = PasswordHelper.VerifyPassword(password, hash, out bool rehashNeeded);
|
||||
|
||||
// assert
|
||||
Assert.IsFalse(isValid);
|
||||
Assert.IsFalse(rehashNeeded);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ShouldReturnTrueOnSuccess()
|
||||
{
|
||||
// arrange
|
||||
string password = "password";
|
||||
string hash = PasswordHelper.HashPassword(password);
|
||||
|
||||
// act
|
||||
bool isValid = PasswordHelper.VerifyPassword(password, hash, out bool rehashNeeded);
|
||||
|
||||
// assert
|
||||
Assert.IsTrue(isValid);
|
||||
Assert.IsFalse(rehashNeeded);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ShouldReturnFalseOnError()
|
||||
{
|
||||
// arrange
|
||||
string password = "pass";
|
||||
string hash = PasswordHelper.HashPassword(password + "word");
|
||||
|
||||
// act
|
||||
bool isValid = PasswordHelper.VerifyPassword(password, hash, out bool rehashNeeded);
|
||||
|
||||
// assert
|
||||
Assert.IsFalse(isValid);
|
||||
Assert.IsFalse(rehashNeeded);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ShouldReturnTrueWithRehash()
|
||||
{
|
||||
// arrange
|
||||
var devHasher = new PasswordHasher<object>();
|
||||
var field = devHasher.GetType().GetField("_compatibilityMode", BindingFlags.NonPublic | BindingFlags.Instance);
|
||||
field.SetValue(devHasher, PasswordHasherCompatibilityMode.IdentityV2);
|
||||
|
||||
string password = "password";
|
||||
string hash = devHasher.HashPassword(null, password);
|
||||
|
||||
// act
|
||||
bool isValid = PasswordHelper.VerifyPassword(password, hash, out bool rehashNeeded);
|
||||
|
||||
// assert
|
||||
Assert.IsTrue(isValid);
|
||||
Assert.IsTrue(rehashNeeded);
|
||||
}
|
||||
}
|
||||
}
|
||||
15
test/AMWD.Common.Tests/AMWD.Common.Tests.csproj
Normal file
15
test/AMWD.Common.Tests/AMWD.Common.Tests.csproj
Normal file
@@ -0,0 +1,15 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="DNS" Version="7.0.0" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\src\AMWD.Common\AMWD.Common.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
305
test/AMWD.Common.Tests/Cli/CommandLineParserTest.cs
Normal file
305
test/AMWD.Common.Tests/Cli/CommandLineParserTest.cs
Normal file
@@ -0,0 +1,305 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using AMWD.Common.Cli;
|
||||
|
||||
namespace AMWD.Common.Tests.Cli
|
||||
{
|
||||
[TestClass]
|
||||
public class CommandLineParserTest
|
||||
{
|
||||
[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();
|
||||
}
|
||||
}
|
||||
}
|
||||
89
test/AMWD.Common.Tests/Cli/EnumerableWalkerTest.cs
Normal file
89
test/AMWD.Common.Tests/Cli/EnumerableWalkerTest.cs
Normal file
@@ -0,0 +1,89 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using AMWD.Common.Cli;
|
||||
|
||||
namespace AMWD.Common.Tests.Cli
|
||||
{
|
||||
[TestClass]
|
||||
public class EnumerableWalkerTest
|
||||
{
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
58
test/AMWD.Common.Tests/Comparer/DomainComparerTest.cs
Normal file
58
test/AMWD.Common.Tests/Comparer/DomainComparerTest.cs
Normal file
@@ -0,0 +1,58 @@
|
||||
using AMWD.Common.Comparer;
|
||||
|
||||
namespace AMWD.Common.Tests.Comparer
|
||||
{
|
||||
[TestClass]
|
||||
public class DomainComparerTest
|
||||
{
|
||||
[DataTestMethod]
|
||||
[DataRow("int", "internal")]
|
||||
[DataRow("int", "dom.int")]
|
||||
[DataRow("a.ins", "a.int")]
|
||||
[DataRow("a.internal", "b.internal")]
|
||||
[DataRow("sub1.domain.internal", "sub2.domain.internal")]
|
||||
public void ShouldBeLessThan(string left, string right)
|
||||
{
|
||||
// Arrange
|
||||
var comparer = new DomainComparer();
|
||||
|
||||
// Act
|
||||
int result = comparer.Compare(left, right);
|
||||
|
||||
Assert.AreEqual(-1, result);
|
||||
}
|
||||
|
||||
[DataTestMethod]
|
||||
[DataRow("internal", "int")]
|
||||
[DataRow("dom.int", "int")]
|
||||
[DataRow("a.int", "a.ins")]
|
||||
[DataRow("b.internal", "a.internal")]
|
||||
[DataRow("sub2.domain.internal", "sub1.domain.internal")]
|
||||
public void ShouldBeGreaterThan(string left, string right)
|
||||
{
|
||||
// Arrange
|
||||
var comparer = new DomainComparer();
|
||||
|
||||
// Act
|
||||
int result = comparer.Compare(left, right);
|
||||
|
||||
Assert.AreEqual(1, result);
|
||||
}
|
||||
|
||||
[DataTestMethod]
|
||||
[DataRow("internal", "internal")]
|
||||
[DataRow("dom.int", "dom.int")]
|
||||
[DataRow("a.internal", "a.internal")]
|
||||
[DataRow("sub.domain.internal", "sub.domain.internal")]
|
||||
public void ShouldBeEqual(string left, string right)
|
||||
{
|
||||
// Arrange
|
||||
var comparer = new DomainComparer();
|
||||
|
||||
// Act
|
||||
int result = comparer.Compare(left, right);
|
||||
|
||||
Assert.AreEqual(0, result);
|
||||
}
|
||||
}
|
||||
}
|
||||
57
test/AMWD.Common.Tests/Comparer/IPAddressComparerTest.cs
Normal file
57
test/AMWD.Common.Tests/Comparer/IPAddressComparerTest.cs
Normal file
@@ -0,0 +1,57 @@
|
||||
using System.Net;
|
||||
using AMWD.Common.Comparer;
|
||||
|
||||
namespace AMWD.Common.Tests.Comparer
|
||||
{
|
||||
[TestClass]
|
||||
public class IPAddressComparerTest
|
||||
{
|
||||
[DataTestMethod]
|
||||
[DataRow("127.0.0.0", "127.0.0.1")]
|
||||
[DataRow("fe80::", "fe80::1")]
|
||||
[DataRow("::ffff:7f00:0", "127.0.0.1")]
|
||||
public void ShouldBeLessThan(string left, string right)
|
||||
{
|
||||
// Arrange
|
||||
var comparer = new IPAddressComparer();
|
||||
|
||||
// Act
|
||||
int result = comparer.Compare(IPAddress.Parse(left), IPAddress.Parse(right));
|
||||
|
||||
// Assert
|
||||
Assert.AreEqual(-1, result);
|
||||
}
|
||||
|
||||
[DataTestMethod]
|
||||
[DataRow("127.0.0.1", "127.0.0.0")]
|
||||
[DataRow("fe80::1", "fe80::")]
|
||||
[DataRow("::ffff:7f00:1", "127.0.0.0")]
|
||||
public void ShouldBeGreaterThan(string left, string right)
|
||||
{
|
||||
// Arrange
|
||||
var comparer = new IPAddressComparer();
|
||||
|
||||
// Act
|
||||
int result = comparer.Compare(IPAddress.Parse(left), IPAddress.Parse(right));
|
||||
|
||||
// Assert
|
||||
Assert.AreEqual(1, result);
|
||||
}
|
||||
|
||||
[DataTestMethod]
|
||||
[DataRow("127.0.0.1", "127.0.0.1")]
|
||||
[DataRow("fe80::1", "fe80::1")]
|
||||
[DataRow("::ffff:7f00:1", "127.0.0.1")]
|
||||
public void ShouldBeEqual(string left, string right)
|
||||
{
|
||||
// Arrange
|
||||
var comparer = new IPAddressComparer();
|
||||
|
||||
// Act
|
||||
int result = comparer.Compare(IPAddress.Parse(left), IPAddress.Parse(right));
|
||||
|
||||
// Assert
|
||||
Assert.AreEqual(0, result);
|
||||
}
|
||||
}
|
||||
}
|
||||
66
test/AMWD.Common.Tests/Comparer/VersionStringComparerTest.cs
Normal file
66
test/AMWD.Common.Tests/Comparer/VersionStringComparerTest.cs
Normal file
@@ -0,0 +1,66 @@
|
||||
using AMWD.Common.Comparer;
|
||||
|
||||
namespace AMWD.Common.Tests.Comparer
|
||||
{
|
||||
[TestClass]
|
||||
public class VersionStringComparerTest
|
||||
{
|
||||
[DataTestMethod]
|
||||
[DataRow(null, "0")]
|
||||
[DataRow("", "0")]
|
||||
[DataRow("0", "1")]
|
||||
[DataRow("1.0", "1.1")]
|
||||
[DataRow("1.0.0", "1.0.1")]
|
||||
[DataRow("1.0.0.0", "1.0.0.1")]
|
||||
[DataRow("1.0.0.0-RC1", "1.0.0.0-RC2")]
|
||||
public void ShouldBeLessThan(string x, string y)
|
||||
{
|
||||
// Arrange
|
||||
var comparer = new VersionStringComparer();
|
||||
|
||||
// Act
|
||||
int result = comparer.Compare(x, y);
|
||||
|
||||
// Assert
|
||||
Assert.AreEqual(-1, result);
|
||||
}
|
||||
|
||||
[DataTestMethod]
|
||||
[DataRow("0", null)]
|
||||
[DataRow("0", "")]
|
||||
[DataRow("1", "0")]
|
||||
[DataRow("1.1", "1.0")]
|
||||
[DataRow("1.0.1", "1.0.0")]
|
||||
[DataRow("1.0.0.1", "1.0.0.0")]
|
||||
[DataRow("1.0.0.0-RC2", "1.0.0.0-RC1")]
|
||||
public void ShouldBeGreaterThan(string x, string y)
|
||||
{
|
||||
// Arrange
|
||||
var comparer = new VersionStringComparer();
|
||||
|
||||
// Act
|
||||
int result = comparer.Compare(x, y);
|
||||
|
||||
// Assert
|
||||
Assert.AreEqual(1, result);
|
||||
}
|
||||
|
||||
[DataTestMethod]
|
||||
[DataRow(null, null)]
|
||||
[DataRow("", "")]
|
||||
[DataRow("1", "1")]
|
||||
[DataRow("1.2.3", "1.2.3")]
|
||||
[DataRow("1.2.3-alpha", "1.2.3-alpha")]
|
||||
public void ShouldBeEqual(string x, string y)
|
||||
{
|
||||
// Arrange
|
||||
var comparer = new VersionStringComparer();
|
||||
|
||||
// Act
|
||||
int result = comparer.Compare(x, y);
|
||||
|
||||
// Assert
|
||||
Assert.AreEqual(0, result);
|
||||
}
|
||||
}
|
||||
}
|
||||
142
test/AMWD.Common.Tests/Extensions/CollectionExtensionsTest.cs
Normal file
142
test/AMWD.Common.Tests/Extensions/CollectionExtensionsTest.cs
Normal file
@@ -0,0 +1,142 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace AMWD.Common.Tests.Extensions
|
||||
{
|
||||
[TestClass]
|
||||
public class CollectionExtensionsTest
|
||||
{
|
||||
[TestMethod]
|
||||
public void ShouldAddItem()
|
||||
{
|
||||
// Arrange
|
||||
var item = new TestItem { Number = 10, Text = "Ten" };
|
||||
ICollection<TestItem> list = new List<TestItem>
|
||||
{
|
||||
new() {
|
||||
Number = 1,
|
||||
Text = "One"
|
||||
}
|
||||
};
|
||||
|
||||
// Act
|
||||
list.AddIfNotNull(item);
|
||||
|
||||
// Assert
|
||||
Assert.AreEqual(2, list.Count);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ShouldNotAddItem()
|
||||
{
|
||||
// Arrange
|
||||
TestItem item = null;
|
||||
ICollection<TestItem> list = new List<TestItem>
|
||||
{
|
||||
new() {
|
||||
Number = 1,
|
||||
Text = "One"
|
||||
}
|
||||
};
|
||||
|
||||
// Act
|
||||
list.AddIfNotNull(item);
|
||||
|
||||
// Assert
|
||||
Assert.AreEqual(1, list.Count);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[ExpectedException(typeof(ArgumentNullException))]
|
||||
public void ShouldThrowArgumentNullExceptionForNullList()
|
||||
{
|
||||
// Arrange
|
||||
var item = new TestItem { Number = 10, Text = "Ten" };
|
||||
ICollection<TestItem> list = null;
|
||||
|
||||
// Act
|
||||
list.AddIfNotNull(item);
|
||||
|
||||
// Assert - ArgumentNullException
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ShouldAddRange()
|
||||
{
|
||||
// Arrange
|
||||
ICollection<TestItem> items = new List<TestItem>
|
||||
{
|
||||
new() { Number = 10, Text = "Ten" },
|
||||
new() { Number = 11, Text = "Eleven" },
|
||||
};
|
||||
ICollection<TestItem> list = new List<TestItem>
|
||||
{
|
||||
new() { Number = 1, Text = "One" },
|
||||
};
|
||||
|
||||
// Act
|
||||
list.AddRange(items);
|
||||
|
||||
// Assert
|
||||
Assert.AreEqual(3, list.Count);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[ExpectedException(typeof(ArgumentNullException))]
|
||||
public void ShouldThrowArgumentNullExceptionForList()
|
||||
{
|
||||
// Arrange
|
||||
ICollection<TestItem> items = new List<TestItem>
|
||||
{
|
||||
new() { Number = 10, Text = "Ten" },
|
||||
new() { Number = 11, Text = "Eleven" },
|
||||
};
|
||||
ICollection<TestItem> list = null;
|
||||
|
||||
// Act
|
||||
list.AddRange(items);
|
||||
|
||||
// Assert - ArgumentNullException
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[ExpectedException(typeof(ArgumentNullException))]
|
||||
public void ShouldThrowArgumentNullExceptionForItems()
|
||||
{
|
||||
// Arrange
|
||||
ICollection<TestItem> items = null;
|
||||
ICollection<TestItem> list = new List<TestItem>
|
||||
{
|
||||
new() { Number = 1, Text = "One" },
|
||||
};
|
||||
|
||||
// Act
|
||||
list.AddRange(items);
|
||||
|
||||
// Assert - ArgumentNullException
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ShouldNotAddRange()
|
||||
{
|
||||
// Arrange
|
||||
ICollection<TestItem> list = new List<TestItem>
|
||||
{
|
||||
new() { Number = 1, Text = "One" },
|
||||
};
|
||||
|
||||
// Act
|
||||
list.AddRange(list);
|
||||
|
||||
// Assert
|
||||
Assert.AreEqual(1, list.Count);
|
||||
}
|
||||
|
||||
private class TestItem
|
||||
{
|
||||
public int Number { get; set; }
|
||||
|
||||
public string Text { get; set; }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
using System.Security.Cryptography;
|
||||
|
||||
namespace AMWD.Common.Tests.Extensions
|
||||
{
|
||||
[TestClass]
|
||||
public class CryptographyHelperExtensionsTest
|
||||
{
|
||||
[TestMethod]
|
||||
public void ShouldReturnMd5Hash()
|
||||
{
|
||||
// arrange
|
||||
string str = "Hello World!";
|
||||
byte[] bytes = [0xaf, 0xfe];
|
||||
|
||||
// act
|
||||
string strHash = str.Md5();
|
||||
string byteHash = bytes.Md5();
|
||||
|
||||
// assert
|
||||
Assert.AreEqual("ed076287532e86365e841e92bfc50d8c", strHash);
|
||||
Assert.AreEqual("63c983de427ce9e2430ba8554d2a822f", byteHash);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ShouldReturnSha1Hash()
|
||||
{
|
||||
// arrange
|
||||
string str = "Hello World!";
|
||||
byte[] bytes = [0xaf, 0xfe];
|
||||
|
||||
// act
|
||||
string strHash = str.Sha1();
|
||||
string byteHash = bytes.Sha1();
|
||||
|
||||
// assert
|
||||
Assert.AreEqual("2ef7bde608ce5404e97d5f042f95f89f1c232871", strHash);
|
||||
Assert.AreEqual("ec2c39d500316044fa49f6c8f471ddec8b4fb9d1", byteHash);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ShouldReturnSha256Hash()
|
||||
{
|
||||
// arrange
|
||||
string str = "Hello World!";
|
||||
byte[] bytes = [0xaf, 0xfe];
|
||||
|
||||
// act
|
||||
string strHash = str.Sha256();
|
||||
string byteHash = bytes.Sha256();
|
||||
|
||||
// assert
|
||||
Assert.AreEqual("7f83b1657ff1fc53b92dc18148a1d65dfc2d4b1fa3d677284addd200126d9069", strHash);
|
||||
Assert.AreEqual("4e0da689dc7a51957be426d6cfb1bd860169cb25dd1ac946a2f141228217804a", byteHash);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ShouldReturnSha512Hash()
|
||||
{
|
||||
// arrange
|
||||
string str = "Hello World!";
|
||||
byte[] bytes = [0xaf, 0xfe];
|
||||
|
||||
// act
|
||||
string strHash = str.Sha512();
|
||||
string byteHash = bytes.Sha512();
|
||||
|
||||
// assert
|
||||
Assert.AreEqual("861844d6704e8573fec34d967e20bcfef3d424cf48be04e6dc08f2bd58c729743371015ead891cc3cf1c9d34b49264b510751b1ff9e537937bc46b5d6ff4ecc8", strHash);
|
||||
Assert.AreEqual("591098c5d470a09f0ff48a4fdb7769ab89f803eae9e23b6f9f69dd228cca46c074bbc11a5fceaa8a5f48d14d2bf19a83a629266c2c5b7d9ef34623b64cb2f8e7", byteHash);
|
||||
}
|
||||
}
|
||||
}
|
||||
362
test/AMWD.Common.Tests/Extensions/DateTimeExtensionsTest.cs
Normal file
362
test/AMWD.Common.Tests/Extensions/DateTimeExtensionsTest.cs
Normal file
@@ -0,0 +1,362 @@
|
||||
using System;
|
||||
using AMWD.Common.Tests.Utils;
|
||||
|
||||
namespace AMWD.Common.Tests.Extensions
|
||||
{
|
||||
[TestClass]
|
||||
public class DateTimeExtensionsTest
|
||||
{
|
||||
[TestMethod]
|
||||
public void ShouldReturnUtc()
|
||||
{
|
||||
// arrange
|
||||
var timeZoneInfo = TimeZoneInfo.FindSystemTimeZoneById("Europe/Berlin");
|
||||
using var _ = TimeZoneInfoLocalMock.Create(timeZoneInfo);
|
||||
|
||||
var utc = new DateTime(2021, 11, 15, 11, 22, 33, DateTimeKind.Utc);
|
||||
var local = new DateTime(2021, 11, 15, 11, 22, 33, DateTimeKind.Local);
|
||||
var unspecified = new DateTime(2021, 11, 15, 11, 22, 33, DateTimeKind.Unspecified);
|
||||
|
||||
// act
|
||||
var utcCorrected = utc.AsUtc();
|
||||
var localCorrected = local.AsUtc();
|
||||
var unspecifiedCorrected = unspecified.AsUtc();
|
||||
|
||||
// assert
|
||||
Assert.AreEqual(DateTimeKind.Utc, utcCorrected.Kind);
|
||||
Assert.AreEqual(DateTimeKind.Utc, localCorrected.Kind);
|
||||
Assert.AreEqual(DateTimeKind.Utc, unspecifiedCorrected.Kind);
|
||||
|
||||
Assert.AreEqual(utc, utcCorrected);
|
||||
Assert.AreEqual(utc.AddHours(-1), localCorrected);
|
||||
Assert.AreEqual(utc, unspecifiedCorrected);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ShouldReturnLocal()
|
||||
{
|
||||
// arrange
|
||||
var timeZoneInfo = TimeZoneInfo.FindSystemTimeZoneById("Europe/Berlin");
|
||||
using var _ = TimeZoneInfoLocalMock.Create(timeZoneInfo);
|
||||
|
||||
var utc = new DateTime(2021, 11, 15, 11, 22, 33, DateTimeKind.Utc);
|
||||
var local = new DateTime(2021, 11, 15, 11, 22, 33, DateTimeKind.Local);
|
||||
var unspecified = new DateTime(2021, 11, 15, 11, 22, 33, DateTimeKind.Unspecified);
|
||||
|
||||
// act
|
||||
var utcCorrected = utc.AsLocal();
|
||||
var localCorrected = local.AsLocal();
|
||||
var unspecifiedCorrected = unspecified.AsLocal();
|
||||
|
||||
// assert
|
||||
Assert.AreEqual(DateTimeKind.Local, utcCorrected.Kind);
|
||||
Assert.AreEqual(DateTimeKind.Local, localCorrected.Kind);
|
||||
Assert.AreEqual(DateTimeKind.Local, unspecifiedCorrected.Kind);
|
||||
|
||||
Assert.AreEqual(local.AddHours(1), utcCorrected);
|
||||
Assert.AreEqual(local, localCorrected);
|
||||
Assert.AreEqual(local, unspecifiedCorrected);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ShouldReturnCorrectUtcAlignmentDaylightSavingEnd()
|
||||
{
|
||||
// arrange
|
||||
var dateTime = new DateTime(2021, 10, 30, 12, 15, 30, 45, DateTimeKind.Utc);
|
||||
|
||||
var intervalThreeSeconds = TimeSpan.FromSeconds(3);
|
||||
var intervalSixMinutes = TimeSpan.FromMinutes(6);
|
||||
var intervalTwoHours = TimeSpan.FromHours(2);
|
||||
var intervalDay = TimeSpan.FromDays(1);
|
||||
|
||||
var offsetTwoMinutes = TimeSpan.FromMinutes(2);
|
||||
var offsetFourHours = TimeSpan.FromHours(4);
|
||||
|
||||
// act
|
||||
var diffThreeSeconds = intervalThreeSeconds.GetAlignedInterval(dateTime);
|
||||
var diffSixMinutes = intervalSixMinutes.GetAlignedInterval(dateTime);
|
||||
var diffTwoHours = intervalTwoHours.GetAlignedInterval(dateTime);
|
||||
var diffDay = intervalDay.GetAlignedInterval(dateTime);
|
||||
|
||||
var diffTwoHoursOffset = intervalTwoHours.GetAlignedInterval(dateTime, offsetTwoMinutes);
|
||||
var diffDayOffset = intervalDay.GetAlignedInterval(dateTime, offsetFourHours);
|
||||
|
||||
// assert
|
||||
Assert.AreEqual(TimeSpan.Parse("00:00:02.955"), diffThreeSeconds);
|
||||
Assert.AreEqual(TimeSpan.Parse("00:02:29.955"), diffSixMinutes);
|
||||
Assert.AreEqual(TimeSpan.Parse("01:44:29.955"), diffTwoHours);
|
||||
Assert.AreEqual(TimeSpan.Parse("11:44:29.955"), diffDay);
|
||||
|
||||
Assert.AreEqual(TimeSpan.Parse("01:46:29.955"), diffTwoHoursOffset);
|
||||
Assert.AreEqual(TimeSpan.Parse("15:44:29.955"), diffDayOffset); // must be the same whether daylight saving has ended
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ShouldReturnCorrectUtcAlignmentDaylightSavingStart()
|
||||
{
|
||||
// arrange
|
||||
var dateTime = new DateTime(2022, 3, 26, 12, 15, 30, 45, DateTimeKind.Utc);
|
||||
|
||||
var intervalThreeSeconds = TimeSpan.FromSeconds(3);
|
||||
var intervalSixMinutes = TimeSpan.FromMinutes(6);
|
||||
var intervalTwoHours = TimeSpan.FromHours(2);
|
||||
var intervalDay = TimeSpan.FromDays(1);
|
||||
|
||||
var offsetTwoMinutes = TimeSpan.FromMinutes(2);
|
||||
var offsetFourHours = TimeSpan.FromHours(4);
|
||||
|
||||
// act
|
||||
var diffThreeSeconds = intervalThreeSeconds.GetAlignedInterval(dateTime);
|
||||
var diffSixMinutes = intervalSixMinutes.GetAlignedInterval(dateTime);
|
||||
var diffTwoHours = intervalTwoHours.GetAlignedInterval(dateTime);
|
||||
var diffDay = intervalDay.GetAlignedInterval(dateTime);
|
||||
|
||||
var diffTwoHoursOffset = intervalTwoHours.GetAlignedInterval(dateTime, offsetTwoMinutes);
|
||||
var diffDayOffset = intervalDay.GetAlignedInterval(dateTime, offsetFourHours);
|
||||
|
||||
// assert
|
||||
Assert.AreEqual(TimeSpan.Parse("00:00:02.955"), diffThreeSeconds);
|
||||
Assert.AreEqual(TimeSpan.Parse("00:02:29.955"), diffSixMinutes);
|
||||
Assert.AreEqual(TimeSpan.Parse("01:44:29.955"), diffTwoHours);
|
||||
Assert.AreEqual(TimeSpan.Parse("11:44:29.955"), diffDay);
|
||||
|
||||
Assert.AreEqual(TimeSpan.Parse("01:46:29.955"), diffTwoHoursOffset);
|
||||
Assert.AreEqual(TimeSpan.Parse("15:44:29.955"), diffDayOffset); // must be the same whether daylight saving has started
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ShouldReturnCorrectLocalAlignmentDaylightSavingEnd()
|
||||
{
|
||||
// arrange
|
||||
var timeZoneInfo = TimeZoneInfo.FindSystemTimeZoneById("Europe/Berlin");
|
||||
using var _ = TimeZoneInfoLocalMock.Create(timeZoneInfo);
|
||||
|
||||
var dateTime = new DateTime(2021, 10, 30, 12, 15, 30, 45, DateTimeKind.Local);
|
||||
|
||||
var intervalThreeSeconds = TimeSpan.FromSeconds(3);
|
||||
var intervalSixMinutes = TimeSpan.FromMinutes(6);
|
||||
var intervalTwoHours = TimeSpan.FromHours(2);
|
||||
var intervalDay = TimeSpan.FromDays(1);
|
||||
|
||||
var offsetTwoMinutes = TimeSpan.FromMinutes(2);
|
||||
var offsetFourHours = TimeSpan.FromHours(4);
|
||||
|
||||
// act
|
||||
var diffThreeSeconds = intervalThreeSeconds.GetAlignedInterval(dateTime);
|
||||
var diffSixMinutes = intervalSixMinutes.GetAlignedInterval(dateTime);
|
||||
var diffTwoHours = intervalTwoHours.GetAlignedInterval(dateTime);
|
||||
var diffDay = intervalDay.GetAlignedInterval(dateTime);
|
||||
|
||||
var diffTwoHoursOffset = intervalTwoHours.GetAlignedInterval(dateTime, offsetTwoMinutes);
|
||||
var diffDayOffset = intervalDay.GetAlignedInterval(dateTime, offsetFourHours);
|
||||
|
||||
// assert
|
||||
Assert.AreEqual(TimeSpan.Parse("00:00:02.955"), diffThreeSeconds);
|
||||
Assert.AreEqual(TimeSpan.Parse("00:02:29.955"), diffSixMinutes);
|
||||
Assert.AreEqual(TimeSpan.Parse("01:44:29.955"), diffTwoHours);
|
||||
Assert.AreEqual(TimeSpan.Parse("11:44:29.955"), diffDay);
|
||||
|
||||
Assert.AreEqual(TimeSpan.Parse("01:46:29.955"), diffTwoHoursOffset);
|
||||
Assert.AreEqual(TimeSpan.Parse("14:44:29.955"), diffDayOffset); // has to be minus one hour due to daylight saving ended
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ShouldReturnCorrectLocalAlignmentDaylightSavingStart()
|
||||
{
|
||||
// arrange
|
||||
var timeZoneInfo = TimeZoneInfo.FindSystemTimeZoneById("Europe/Berlin");
|
||||
using var _ = TimeZoneInfoLocalMock.Create(timeZoneInfo);
|
||||
|
||||
var dateTime = new DateTime(2022, 3, 26, 12, 15, 30, 45, DateTimeKind.Local);
|
||||
|
||||
var intervalThreeSeconds = TimeSpan.FromSeconds(3);
|
||||
var intervalSixMinutes = TimeSpan.FromMinutes(6);
|
||||
var intervalTwoHours = TimeSpan.FromHours(2);
|
||||
var intervalDay = TimeSpan.FromDays(1);
|
||||
|
||||
var offsetTwoMinutes = TimeSpan.FromMinutes(2);
|
||||
var offsetFourHours = TimeSpan.FromHours(4);
|
||||
|
||||
// act
|
||||
var diffThreeSeconds = intervalThreeSeconds.GetAlignedInterval(dateTime);
|
||||
var diffSixMinutes = intervalSixMinutes.GetAlignedInterval(dateTime);
|
||||
var diffTwoHours = intervalTwoHours.GetAlignedInterval(dateTime);
|
||||
var diffDay = intervalDay.GetAlignedInterval(dateTime);
|
||||
|
||||
var diffTwoHoursOffset = intervalTwoHours.GetAlignedInterval(dateTime, offsetTwoMinutes);
|
||||
var diffDayOffset = intervalDay.GetAlignedInterval(dateTime, offsetFourHours);
|
||||
|
||||
// assert
|
||||
Assert.AreEqual(TimeSpan.Parse("00:00:02.955"), diffThreeSeconds);
|
||||
Assert.AreEqual(TimeSpan.Parse("00:02:29.955"), diffSixMinutes);
|
||||
Assert.AreEqual(TimeSpan.Parse("01:44:29.955"), diffTwoHours);
|
||||
Assert.AreEqual(TimeSpan.Parse("11:44:29.955"), diffDay);
|
||||
|
||||
Assert.AreEqual(TimeSpan.Parse("01:46:29.955"), diffTwoHoursOffset);
|
||||
Assert.AreEqual(TimeSpan.Parse("16:44:29.955"), diffDayOffset); // has to be plus one hour due to daylight saving started
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ShouldReturnCorrectAlignment()
|
||||
{
|
||||
// arrange
|
||||
var timeZoneInfo = TimeZoneInfo.FindSystemTimeZoneById("Europe/Berlin");
|
||||
using var _ = TimeZoneInfoLocalMock.Create(timeZoneInfo);
|
||||
|
||||
var interval = TimeSpan.FromDays(1);
|
||||
|
||||
// act
|
||||
var intervalUtc = interval.GetAlignedIntervalUtc();
|
||||
var expectedUtc = DateTime.UtcNow.TimeOfDay;
|
||||
var intervalLocal = interval.GetAlignedIntervalLocal();
|
||||
var expectedLocal = DateTime.Now.TimeOfDay;
|
||||
|
||||
// assert
|
||||
Assert.AreEqual(expectedUtc.RoundToSecond(), (interval - intervalUtc).RoundToSecond());
|
||||
Assert.AreEqual(expectedLocal.RoundToSecond(), (interval - intervalLocal).RoundToSecond());
|
||||
|
||||
if (DateTime.Now.TimeOfDay < DateTime.UtcNow.TimeOfDay) // case when local time has new day but UTC not
|
||||
{
|
||||
Assert.AreEqual((DateTime.Now - DateTime.UtcNow).RoundToSecond(), interval - (intervalLocal - intervalUtc).RoundToSecond());
|
||||
}
|
||||
else
|
||||
{
|
||||
Assert.AreEqual((DateTime.Now - DateTime.UtcNow).RoundToSecond(), (intervalUtc - intervalLocal).RoundToSecond());
|
||||
}
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ShouldReturnCorrectShortStringForTimeSpan()
|
||||
{
|
||||
// arrange
|
||||
var timeSpan = TimeSpan.Parse("1.10:11:12.345");
|
||||
var negativeTimeSpan = TimeSpan.FromDays(-1.234);
|
||||
|
||||
// act
|
||||
string shortString = timeSpan.ToShortString(withMilliseconds: false);
|
||||
string shortStringWithMillis = timeSpan.ToShortString(withMilliseconds: true);
|
||||
string shortStringNegative = negativeTimeSpan.ToShortString(withMilliseconds: true);
|
||||
|
||||
// assert
|
||||
Assert.AreEqual("1d 10h 11m 12s", shortString);
|
||||
Assert.AreEqual("1d 10h 11m 12s 345ms", shortStringWithMillis);
|
||||
Assert.AreEqual("-1d 5h 36m 57s 600ms", shortStringNegative);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ShouldRoundToSecond()
|
||||
{
|
||||
// arrange
|
||||
var timestampUp = new DateTime(2021, 11, 16, 20, 15, 30, 500);
|
||||
var timestampDown = new DateTime(2021, 11, 16, 20, 15, 30, 499);
|
||||
|
||||
var timespanUp = new TimeSpan(1, 2, 3, 4, 500);
|
||||
var timespanDown = new TimeSpan(1, 2, 3, 4, 499);
|
||||
|
||||
// act
|
||||
var timestampUpRounded = timestampUp.RoundToSecond();
|
||||
var timestampDownRounded = timestampDown.RoundToSecond();
|
||||
|
||||
var timespanUpRounded = timespanUp.RoundToSecond();
|
||||
var timespanDownRounded = timespanDown.RoundToSecond();
|
||||
|
||||
// assert
|
||||
Assert.AreNotEqual(timestampUp, timestampUpRounded);
|
||||
Assert.AreEqual(DateTime.Parse("2021-11-16 20:15:31.000"), timestampUpRounded);
|
||||
Assert.AreNotEqual(timestampDown, timestampDownRounded);
|
||||
Assert.AreEqual(DateTime.Parse("2021-11-16 20:15:30.000"), timestampDownRounded);
|
||||
|
||||
Assert.AreNotEqual(timespanUp, timespanUpRounded);
|
||||
Assert.AreEqual(TimeSpan.Parse("1.02:03:05.000"), timespanUpRounded);
|
||||
Assert.AreNotEqual(timespanDown, timespanDownRounded);
|
||||
Assert.AreEqual(TimeSpan.Parse("1.02:03:04.000"), timespanDownRounded);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ShouldRoundToMinute()
|
||||
{
|
||||
// arrange
|
||||
var timestampUp = new DateTime(2021, 11, 16, 20, 15, 30, 0);
|
||||
var timestampDown = new DateTime(2021, 11, 16, 20, 15, 29, 999);
|
||||
|
||||
var timespanUp = new TimeSpan(1, 2, 3, 30, 0);
|
||||
var timespanDown = new TimeSpan(1, 2, 3, 29, 999);
|
||||
|
||||
// act
|
||||
var timestampUpRounded = timestampUp.RoundToMinute();
|
||||
var timestampDownRounded = timestampDown.RoundToMinute();
|
||||
|
||||
var timespanUpRounded = timespanUp.RoundToMinute();
|
||||
var timespanDownRounded = timespanDown.RoundToMinute();
|
||||
|
||||
// assert
|
||||
Assert.AreNotEqual(timestampUp, timestampUpRounded);
|
||||
Assert.AreEqual(DateTime.Parse("2021-11-16 20:16:00.000"), timestampUpRounded);
|
||||
Assert.AreNotEqual(timestampDown, timestampDownRounded);
|
||||
Assert.AreEqual(DateTime.Parse("2021-11-16 20:15:00.000"), timestampDownRounded);
|
||||
|
||||
Assert.AreNotEqual(timespanUp, timespanUpRounded);
|
||||
Assert.AreEqual(TimeSpan.Parse("1.02:04:00.000"), timespanUpRounded);
|
||||
Assert.AreNotEqual(timespanDown, timespanDownRounded);
|
||||
Assert.AreEqual(TimeSpan.Parse("1.02:03:00.000"), timespanDownRounded);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ShouldRoundToHour()
|
||||
{
|
||||
// arrange
|
||||
var timestampUp = new DateTime(2021, 11, 16, 20, 30, 0, 0);
|
||||
var timestampDown = new DateTime(2021, 11, 16, 20, 29, 59, 999);
|
||||
|
||||
var timespanUp = new TimeSpan(1, 2, 30, 0, 0);
|
||||
var timespanDown = new TimeSpan(1, 2, 29, 59, 999);
|
||||
|
||||
// act
|
||||
var timestampUpRounded = timestampUp.RoundToHour();
|
||||
var timestampDownRounded = timestampDown.RoundToHour();
|
||||
|
||||
var timespanUpRounded = timespanUp.RoundToHour();
|
||||
var timespanDownRounded = timespanDown.RoundToHour();
|
||||
|
||||
// assert
|
||||
Assert.AreNotEqual(timestampUp, timestampUpRounded);
|
||||
Assert.AreEqual(DateTime.Parse("2021-11-16 21:00:00.000"), timestampUpRounded);
|
||||
Assert.AreNotEqual(timestampDown, timestampDownRounded);
|
||||
Assert.AreEqual(DateTime.Parse("2021-11-16 20:00:00.000"), timestampDownRounded);
|
||||
|
||||
Assert.AreNotEqual(timespanUp, timespanUpRounded);
|
||||
Assert.AreEqual(TimeSpan.Parse("1.03:00:00.000"), timespanUpRounded);
|
||||
Assert.AreNotEqual(timespanDown, timespanDownRounded);
|
||||
Assert.AreEqual(TimeSpan.Parse("1.02:00:00.000"), timespanDownRounded);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ShouldRoundToDay()
|
||||
{
|
||||
// arrange
|
||||
var timestampUp = new DateTime(2021, 11, 16, 12, 0, 0, 0);
|
||||
var timestampDown = new DateTime(2021, 11, 16, 11, 59, 59, 999);
|
||||
|
||||
var timespanUp = new TimeSpan(1, 12, 0, 0, 0);
|
||||
var timespanDown = new TimeSpan(1, 11, 59, 59, 999);
|
||||
|
||||
// act
|
||||
var timestampUpRounded = timestampUp.RoundToDay();
|
||||
var timestampDownRounded = timestampDown.RoundToDay();
|
||||
|
||||
var timespanUpRounded = timespanUp.RoundToDay();
|
||||
var timespanDownRounded = timespanDown.RoundToDay();
|
||||
|
||||
// assert
|
||||
Assert.AreNotEqual(timestampUp, timestampUpRounded);
|
||||
Assert.AreEqual(DateTime.Parse("2021-11-17 00:00:00.000"), timestampUpRounded);
|
||||
Assert.AreNotEqual(timestampDown, timestampDownRounded);
|
||||
Assert.AreEqual(DateTime.Parse("2021-11-16 00:00:00.000"), timestampDownRounded);
|
||||
|
||||
Assert.AreNotEqual(timespanUp, timespanUpRounded);
|
||||
Assert.AreEqual(TimeSpan.Parse("2.00:00:00.000"), timespanUpRounded);
|
||||
Assert.AreNotEqual(timespanDown, timespanDownRounded);
|
||||
Assert.AreEqual(TimeSpan.Parse("1.00:00:00.000"), timespanDownRounded);
|
||||
}
|
||||
}
|
||||
}
|
||||
119
test/AMWD.Common.Tests/Extensions/EnumExtensionsTest.cs
Normal file
119
test/AMWD.Common.Tests/Extensions/EnumExtensionsTest.cs
Normal file
@@ -0,0 +1,119 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using AMWD.Common.Tests.Utils;
|
||||
using DescriptionAttribute = System.ComponentModel.DescriptionAttribute;
|
||||
|
||||
namespace AMWD.Common.Tests.Extensions
|
||||
{
|
||||
[TestClass]
|
||||
public class EnumExtensionsTest
|
||||
{
|
||||
[TestMethod]
|
||||
public void ShouldReturnEmptyList()
|
||||
{
|
||||
// arrange
|
||||
var enumValue = TestEnum.Two;
|
||||
|
||||
// act
|
||||
var customList = enumValue.GetAttributes<CustomMultipleAttribute>();
|
||||
var descriptionList = enumValue.GetAttributes<DescriptionAttribute>();
|
||||
|
||||
// assert
|
||||
Assert.IsNotNull(customList);
|
||||
Assert.IsFalse(customList.Any());
|
||||
Assert.IsNotNull(descriptionList);
|
||||
Assert.IsFalse(descriptionList.Any());
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ShouldReturnList()
|
||||
{
|
||||
// arrange
|
||||
var enumValue = TestEnum.Zero;
|
||||
|
||||
// act
|
||||
var customList = enumValue.GetAttributes<CustomMultipleAttribute>();
|
||||
var descriptionList = enumValue.GetAttributes<DescriptionAttribute>();
|
||||
|
||||
// assert
|
||||
Assert.IsNotNull(customList);
|
||||
Assert.IsTrue(customList.Any());
|
||||
Assert.AreEqual(2, customList.Count());
|
||||
Assert.IsNotNull(descriptionList);
|
||||
Assert.IsTrue(descriptionList.Any());
|
||||
Assert.AreEqual(1, descriptionList.Count());
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ShouldReturnNothing()
|
||||
{
|
||||
// arrange
|
||||
var enumValue = TestEnum.Two;
|
||||
|
||||
// act
|
||||
var customAttribute = enumValue.GetAttribute<CustomMultipleAttribute>();
|
||||
var descriptionAttribute = enumValue.GetAttribute<DescriptionAttribute>();
|
||||
|
||||
// assert
|
||||
Assert.IsNull(customAttribute);
|
||||
Assert.IsNull(descriptionAttribute);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ShouldReturnFirstAttribute()
|
||||
{
|
||||
// arrange
|
||||
var enumValue = TestEnum.Zero;
|
||||
|
||||
// act
|
||||
var customAttribute = enumValue.GetAttribute<CustomMultipleAttribute>();
|
||||
var descriptionAttribute = enumValue.GetAttribute<DescriptionAttribute>();
|
||||
|
||||
// assert
|
||||
Assert.IsNotNull(customAttribute);
|
||||
Assert.AreEqual("nix", customAttribute.Name);
|
||||
Assert.IsNotNull(descriptionAttribute);
|
||||
Assert.AreEqual("Null", descriptionAttribute.Description);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ShouldReturnDescriptionOrStringRepresentation()
|
||||
{
|
||||
// arrange
|
||||
var enumWithDescription = TestEnum.One;
|
||||
var enumWithoutDescripton = TestEnum.Two;
|
||||
|
||||
// act
|
||||
string description = enumWithDescription.GetDescription();
|
||||
string noDescription = enumWithoutDescripton.GetDescription();
|
||||
|
||||
// assert
|
||||
Assert.AreEqual("Eins", description);
|
||||
Assert.AreEqual(enumWithoutDescripton.ToString(), noDescription);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ShouldReturnEmptyListOnNotDefinedEnumValue()
|
||||
{
|
||||
// arrange
|
||||
var notDefinedEnum = (TestEnum)10;
|
||||
|
||||
// act
|
||||
var list = notDefinedEnum.GetAttributes<DescriptionAttribute>();
|
||||
|
||||
// assert
|
||||
Assert.IsFalse(list.Any());
|
||||
}
|
||||
|
||||
internal enum TestEnum
|
||||
{
|
||||
[CustomMultiple("nix")]
|
||||
[CustomMultiple("Null")]
|
||||
[Description("Null")]
|
||||
Zero,
|
||||
[Description("Eins")]
|
||||
One,
|
||||
Two,
|
||||
}
|
||||
}
|
||||
}
|
||||
73
test/AMWD.Common.Tests/Extensions/ExceptionExtensionsTest.cs
Normal file
73
test/AMWD.Common.Tests/Extensions/ExceptionExtensionsTest.cs
Normal file
@@ -0,0 +1,73 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace AMWD.Common.Tests.Extensions
|
||||
{
|
||||
[TestClass]
|
||||
public class ExceptionExtensionsTest
|
||||
{
|
||||
[TestMethod]
|
||||
public void ShouldReturnExceptionMessage()
|
||||
{
|
||||
// arrange
|
||||
var exception = new Exception("This is a message.");
|
||||
|
||||
// act
|
||||
string message = exception.GetMessage();
|
||||
|
||||
// assert
|
||||
Assert.AreEqual(exception.Message, message);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ShouldReturnInnerExceptionMessage()
|
||||
{
|
||||
// arrange
|
||||
var innerException = new Exception("Message from the inner side.");
|
||||
var outerException = new Exception("Message from the outer side.", innerException);
|
||||
|
||||
// act
|
||||
string message = outerException.GetMessage();
|
||||
|
||||
// assert
|
||||
Assert.AreEqual(innerException.Message, message);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ShouldReturnRecursiveExceptionMessageFoInnerException()
|
||||
{
|
||||
// arrange
|
||||
var innerException = new Exception("Message from the inner side.");
|
||||
var outerException = new Exception("Message from the outer side. See the inner exception for details.", innerException);
|
||||
string expectedMessage = $"Message from the outer side. Message from the inner side.";
|
||||
|
||||
// act
|
||||
string message = outerException.GetRecursiveMessage();
|
||||
|
||||
// assert
|
||||
Assert.AreEqual(expectedMessage, message);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ShouldReturnRecursiveExceptionMessageFoInnerExceptions()
|
||||
{
|
||||
// arrange
|
||||
var innerExceptions = new List<Exception>
|
||||
{
|
||||
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.";
|
||||
|
||||
// act
|
||||
string message = aggregateException.GetRecursiveMessage();
|
||||
|
||||
// assert
|
||||
Assert.AreEqual(expectedMessage, message);
|
||||
}
|
||||
}
|
||||
}
|
||||
86
test/AMWD.Common.Tests/Extensions/IPAddressExtensionsTest.cs
Normal file
86
test/AMWD.Common.Tests/Extensions/IPAddressExtensionsTest.cs
Normal file
@@ -0,0 +1,86 @@
|
||||
using System.Net;
|
||||
|
||||
namespace AMWD.Common.Tests.Extensions
|
||||
{
|
||||
[TestClass]
|
||||
public class IPAddressExtensionsTest
|
||||
{
|
||||
[TestMethod]
|
||||
public void ShouldIncrementLastByte()
|
||||
{
|
||||
// arrange
|
||||
var ipAddress = IPAddress.Parse("192.168.178.22");
|
||||
|
||||
// act
|
||||
var incremented = ipAddress.Increment();
|
||||
|
||||
// assert
|
||||
Assert.AreEqual("192.168.178.23", incremented.ToString());
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ShouldIncrementAllBytes()
|
||||
{
|
||||
// arrange
|
||||
var ipAddress = IPAddress.Parse("192.255.255.255");
|
||||
|
||||
// act
|
||||
var incremented = ipAddress.Increment();
|
||||
|
||||
// assert
|
||||
Assert.AreEqual("193.0.0.0", incremented.ToString());
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ShouldIncrementOverflow()
|
||||
{
|
||||
// arrange
|
||||
var ipAddress = IPAddress.Parse("255.255.255.255");
|
||||
|
||||
// act
|
||||
var incremented = ipAddress.Increment();
|
||||
|
||||
// assert
|
||||
Assert.AreEqual("0.0.0.0", incremented.ToString());
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ShouldDecrementLastByte()
|
||||
{
|
||||
// arrange
|
||||
var ipAddress = IPAddress.Parse("192.168.178.22");
|
||||
|
||||
// act
|
||||
var decremented = ipAddress.Decrement();
|
||||
|
||||
// assert
|
||||
Assert.AreEqual("192.168.178.21", decremented.ToString());
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ShouldDecrementAllBytes()
|
||||
{
|
||||
// arrange
|
||||
var ipAddress = IPAddress.Parse("192.0.0.0");
|
||||
|
||||
// act
|
||||
var decremented = ipAddress.Decrement();
|
||||
|
||||
// assert
|
||||
Assert.AreEqual("191.255.255.255", decremented.ToString());
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ShouldDecrementUnderflow()
|
||||
{
|
||||
// arrange
|
||||
var ipAddress = IPAddress.Parse("0.0.0.0");
|
||||
|
||||
// act
|
||||
var decremented = ipAddress.Decrement();
|
||||
|
||||
// assert
|
||||
Assert.AreEqual("255.255.255.255", decremented.ToString());
|
||||
}
|
||||
}
|
||||
}
|
||||
339
test/AMWD.Common.Tests/Extensions/JsonExtensionsTest.cs
Normal file
339
test/AMWD.Common.Tests/Extensions/JsonExtensionsTest.cs
Normal file
@@ -0,0 +1,339 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using AMWD.Common.Tests.Utils;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
|
||||
namespace AMWD.Common.Tests.Extensions
|
||||
{
|
||||
[TestClass]
|
||||
public class JsonExtensionsTest
|
||||
{
|
||||
[TestMethod]
|
||||
public void ShouldReturnJson()
|
||||
{
|
||||
// arrange
|
||||
var testObject = new JsonTestClass();
|
||||
string expected = @"{""stringValue"":""Hello World!"",""isBoolTrue"":true,""floatValue"":12.34,""doubleValue"":21.42,""decimalValue"":42.21,""localTimestamp"":""2021-11-16T20:15:34+01:00"",""utcTimestamp"":""2021-11-16T20:15:34Z"",""object"":{""integerValue"":42,""stringValue"":""Foo-Bar""}}";
|
||||
|
||||
// act
|
||||
string json = testObject.SerializeJson();
|
||||
|
||||
// assert
|
||||
Assert.IsFalse(string.IsNullOrWhiteSpace(json));
|
||||
Assert.AreEqual(expected, json);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ShouldReturnJsonIndented()
|
||||
{
|
||||
// arrange
|
||||
var testObject = new JsonTestClass();
|
||||
string expected = @"{
|
||||
""stringValue"": ""Hello World!"",
|
||||
""isBoolTrue"": true,
|
||||
""floatValue"": 12.34,
|
||||
""doubleValue"": 21.42,
|
||||
""decimalValue"": 42.21,
|
||||
""localTimestamp"": ""2021-11-16T20:15:34+01:00"",
|
||||
""utcTimestamp"": ""2021-11-16T20:15:34Z"",
|
||||
""object"": {
|
||||
""integerValue"": 42,
|
||||
""stringValue"": ""Foo-Bar""
|
||||
}
|
||||
}";
|
||||
|
||||
// act
|
||||
string json = testObject.SerializeJson(indented: true);
|
||||
|
||||
// assert
|
||||
Assert.IsFalse(string.IsNullOrWhiteSpace(json));
|
||||
Assert.AreEqual(expected.Replace("\r", ""), json.Replace("\r", ""));
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ShouldReturnJsonWithSingleQuotes()
|
||||
{
|
||||
// arrange
|
||||
var testObject = new JsonTestClass
|
||||
{
|
||||
StringValue = "Sam's Pub"
|
||||
};
|
||||
string expected = "{'stringValue':'Sam\\'s Pub','isBoolTrue':true,'floatValue':12.34,'doubleValue':21.42,'decimalValue':42.21,'localTimestamp':'2021-11-16T20:15:34+01:00','utcTimestamp':'2021-11-16T20:15:34Z','object':{'integerValue':42,'stringValue':'Foo-Bar'}}";
|
||||
|
||||
// act
|
||||
string json = testObject.SerializeJson(useSingleQuotes: true);
|
||||
|
||||
// assert
|
||||
Assert.IsFalse(string.IsNullOrWhiteSpace(json));
|
||||
Assert.AreEqual(expected, json);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ShouldReturnJsonWithoutCamelCase()
|
||||
{
|
||||
// arrange
|
||||
var testObject = new JsonTestClass();
|
||||
string expected = @"{""StringValue"":""Hello World!"",""IsBoolTrue"":true,""FloatValue"":12.34,""DoubleValue"":21.42,""DecimalValue"":42.21,""LocalTimestamp"":""2021-11-16T20:15:34+01:00"",""UtcTimestamp"":""2021-11-16T20:15:34Z"",""Object"":{""IntegerValue"":42,""StringValue"":""Foo-Bar""}}";
|
||||
|
||||
// act
|
||||
string json = testObject.SerializeJson(useCamelCase: false);
|
||||
|
||||
// assert
|
||||
Assert.IsFalse(string.IsNullOrWhiteSpace(json));
|
||||
Assert.AreEqual(expected, json);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ShouldReturnJsonWithType()
|
||||
{
|
||||
// arrange
|
||||
var testObject = new JsonTestClass();
|
||||
string expected = @"{""$type"":""AMWD.Common.Tests.Utils.JsonTestClass, AMWD.Common.Tests"",""stringValue"":""Hello World!"",""isBoolTrue"":true,""floatValue"":12.34,""doubleValue"":21.42,""decimalValue"":42.21,""localTimestamp"":""2021-11-16T20:15:34+01:00"",""utcTimestamp"":""2021-11-16T20:15:34Z"",""object"":{""$type"":""AMWD.Common.Tests.Utils.JsonTestSubClass, AMWD.Common.Tests"",""integerValue"":42,""stringValue"":""Foo-Bar""}}";
|
||||
|
||||
// act
|
||||
string json = testObject.SerializeJson(includeType: true);
|
||||
|
||||
// assert
|
||||
Assert.IsFalse(string.IsNullOrWhiteSpace(json));
|
||||
Assert.AreEqual(expected, json);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ShouldDeserializeWithoutFallback()
|
||||
{
|
||||
// arrange
|
||||
string workingJson = @"{""stringValue"":""Some fancy string"",""isBoolTrue"":false,""floatValue"":12.34,""doubleValue"":21.42,""decimalValue"":123.45,""localTimestamp"":""2021-11-15T20:15:34+01:00"",""utcTimestamp"":""2021-10-16T20:15:34Z"",""object"":{""integerValue"":21,""stringValue"":""FooBar""}}";
|
||||
string brokenJson = @"{""strValue"":""Some fancy string"",""isBoolTrue"":false,""floatValue"":12.34,""doubleValue"":21.42,""decimalValue"":123.45,""localTimestamp"":""2021-11-15T20:15:34+01:00"",""utcTimestamp"":""2021-10-16T20:15:34Z"",""object"":{""integerValue"":21.12,""stringValue"":""FooBar""}}";
|
||||
string emptyString = "";
|
||||
|
||||
// act
|
||||
var workingObj = workingJson.DeserializeJson<JsonTestClass>();
|
||||
var emptyObj = emptyString.DeserializeJson<JsonTestClass>();
|
||||
|
||||
try
|
||||
{
|
||||
var brokenObj = brokenJson.DeserializeJson<JsonTestClass>();
|
||||
Assert.Fail();
|
||||
}
|
||||
catch (JsonReaderException)
|
||||
{ }
|
||||
|
||||
// assert
|
||||
Assert.IsNotNull(workingObj);
|
||||
Assert.IsNull(emptyObj);
|
||||
|
||||
Assert.AreEqual("Some fancy string", workingObj.StringValue);
|
||||
Assert.IsFalse(workingObj.IsBoolTrue);
|
||||
Assert.AreEqual(123.45m, workingObj.DecimalValue);
|
||||
Assert.AreEqual(DateTimeKind.Local, workingObj.LocalTimestamp.Kind);
|
||||
Assert.AreEqual("15.11.2021 20:15:34", workingObj.LocalTimestamp.ToString("dd.MM.yyyy HH:mm:ss"));
|
||||
Assert.AreEqual(DateTimeKind.Utc, workingObj.UtcTimestamp.Kind);
|
||||
Assert.AreEqual("16.10.2021 20:15:34", workingObj.UtcTimestamp.ToString("dd.MM.yyyy HH:mm:ss"));
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ShouldDeserializeWithFallback()
|
||||
{
|
||||
// arrange
|
||||
string workingJson = @"{""stringValue"":""Some fancy string"",""isBoolTrue"":false,""floatValue"":12.34,""doubleValue"":21.42,""decimalValue"":123.45,""localTimestamp"":""2021-11-15T20:15:34+01:00"",""utcTimestamp"":""2021-10-16T20:15:34Z"",""object"":{""integerValue"":21,""stringValue"":""FooBar""}}";
|
||||
string brokenJson = @"{""strValue"":""Some fancy string"",""isBoolTrue"":false,""floatValue"":12.34,""doubleValue"":21.42,""decimalValue"":123.45,""localTimestamp"":""2021-11-15T20:15:34+01:00"",""utcTimestamp"":""2021-10-16T20:15:34Z"",""object"":{""integerValue"":21.12,""stringValue"":""FooBar""}}";
|
||||
string emptyString = "";
|
||||
var fallback = new JsonTestClass
|
||||
{
|
||||
DoubleValue = 0.815
|
||||
};
|
||||
|
||||
// act
|
||||
var workingObj = workingJson.DeserializeJson(fallback);
|
||||
var brokenObj = brokenJson.DeserializeJson(fallback);
|
||||
var emptyObj = emptyString.DeserializeJson(fallback);
|
||||
|
||||
// assert
|
||||
Assert.IsNotNull(workingObj);
|
||||
Assert.IsNotNull(brokenObj);
|
||||
Assert.IsNull(emptyObj);
|
||||
|
||||
Assert.AreEqual("Some fancy string", workingObj.StringValue);
|
||||
Assert.IsFalse(workingObj.IsBoolTrue);
|
||||
Assert.AreEqual(123.45m, workingObj.DecimalValue);
|
||||
Assert.AreEqual(21.42, workingObj.DoubleValue);
|
||||
Assert.AreEqual(DateTimeKind.Local, workingObj.LocalTimestamp.Kind);
|
||||
Assert.AreEqual("15.11.2021 20:15:34", workingObj.LocalTimestamp.ToString("dd.MM.yyyy HH:mm:ss"));
|
||||
Assert.AreEqual(DateTimeKind.Utc, workingObj.UtcTimestamp.Kind);
|
||||
Assert.AreEqual("16.10.2021 20:15:34", workingObj.UtcTimestamp.ToString("dd.MM.yyyy HH:mm:ss"));
|
||||
|
||||
Assert.AreEqual(fallback.StringValue, brokenObj.StringValue);
|
||||
Assert.AreEqual(fallback.IsBoolTrue, brokenObj.IsBoolTrue);
|
||||
Assert.AreEqual(fallback.DecimalValue, brokenObj.DecimalValue);
|
||||
Assert.AreEqual(fallback.DoubleValue, brokenObj.DoubleValue);
|
||||
Assert.AreEqual(fallback.LocalTimestamp.Kind, brokenObj.LocalTimestamp.Kind);
|
||||
Assert.AreEqual(fallback.LocalTimestamp, brokenObj.LocalTimestamp);
|
||||
Assert.AreEqual(fallback.UtcTimestamp.Kind, brokenObj.UtcTimestamp.Kind);
|
||||
Assert.AreEqual(fallback.UtcTimestamp, brokenObj.UtcTimestamp);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ShouldDeserializeUsingPopulate()
|
||||
{
|
||||
// arrange
|
||||
string json = @"{""stringValue"":""Some fancy string"",""isBoolTrue"":false,""floatValue"":12.34,""doubleValue"":21.42,""decimalValue"":123.45,""localTimestamp"":""2021-11-15T20:15:34+01:00"",""utcTimestamp"":""2021-10-16T20:15:34Z"",""object"":{""integerValue"":21,""stringValue"":""FooBar""}}";
|
||||
var obj = new JsonTestClass();
|
||||
|
||||
// act
|
||||
obj.DeserializeJson(json);
|
||||
|
||||
// assert
|
||||
Assert.AreEqual("Some fancy string", obj.StringValue);
|
||||
Assert.IsFalse(obj.IsBoolTrue);
|
||||
Assert.AreEqual(123.45m, obj.DecimalValue);
|
||||
Assert.AreEqual(21.42, obj.DoubleValue);
|
||||
Assert.AreEqual(DateTimeKind.Local, obj.LocalTimestamp.Kind);
|
||||
Assert.AreEqual("15.11.2021 20:15:34", obj.LocalTimestamp.ToString("dd.MM.yyyy HH:mm:ss"));
|
||||
Assert.AreEqual(DateTimeKind.Utc, obj.UtcTimestamp.Kind);
|
||||
Assert.AreEqual("16.10.2021 20:15:34", obj.UtcTimestamp.ToString("dd.MM.yyyy HH:mm:ss"));
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ShouldConvertToJObject()
|
||||
{
|
||||
// arrange
|
||||
var obj = new JsonTestClass
|
||||
{
|
||||
StringValue = "Hello JSON",
|
||||
DecimalValue = 0.815m
|
||||
};
|
||||
|
||||
// act
|
||||
var jObj = obj.ConvertToJObject();
|
||||
|
||||
// assert
|
||||
Assert.IsNotNull(jObj);
|
||||
Assert.AreEqual(typeof(JObject), jObj.GetType());
|
||||
Assert.AreEqual(obj.StringValue, jObj.Value<string>("stringValue"));
|
||||
Assert.AreEqual(obj.DecimalValue, jObj.Value<decimal>("decimalValue"));
|
||||
Assert.AreEqual(obj.LocalTimestamp.Kind, jObj.Value<DateTime>("localTimestamp").Kind);
|
||||
Assert.AreEqual(obj.LocalTimestamp, jObj.Value<DateTime>("localTimestamp"));
|
||||
Assert.AreEqual(obj.UtcTimestamp.Kind, jObj.Value<DateTime>("utcTimestamp").Kind);
|
||||
Assert.AreEqual(obj.UtcTimestamp, jObj.Value<DateTime>("utcTimestamp"));
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ShouldConvertToJArray()
|
||||
{
|
||||
// arrange
|
||||
string[] stringArray = ["one", "two", "three"];
|
||||
var objectArray = new[]
|
||||
{
|
||||
new JsonTestClass { StringValue = "One" },
|
||||
new JsonTestClass { StringValue = "Two" },
|
||||
new JsonTestClass { StringValue = "Three" }
|
||||
};
|
||||
|
||||
// act
|
||||
var stringJArray = stringArray.ConvertToJArray();
|
||||
var objectJArray = objectArray.ConvertToJArray();
|
||||
|
||||
// assert
|
||||
Assert.IsNotNull(stringJArray);
|
||||
Assert.AreEqual(typeof(JArray), stringJArray.GetType());
|
||||
Assert.AreEqual(stringArray[0], stringJArray[0]);
|
||||
Assert.AreEqual(stringArray[1], stringJArray[1]);
|
||||
Assert.AreEqual(stringArray[2], stringJArray[2]);
|
||||
|
||||
Assert.IsNotNull(objectJArray);
|
||||
Assert.AreEqual(typeof(JArray), objectJArray.GetType());
|
||||
Assert.AreEqual(objectArray[0].StringValue, objectJArray[0].Value<string>("stringValue"));
|
||||
Assert.AreEqual(objectArray[1].StringValue, objectJArray[1].Value<string>("stringValue"));
|
||||
Assert.AreEqual(objectArray[2].StringValue, objectJArray[2].Value<string>("stringValue"));
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ShouldRunJsonTreeWithoutDefault()
|
||||
{
|
||||
// arrange
|
||||
var obj = new JsonTestClass { StringValue = "Running Json", Object = new JsonTestSubClass { IntegerValue = 4711 } };
|
||||
var jObj = obj.ConvertToJObject();
|
||||
|
||||
// act
|
||||
string topLevelString = jObj.GetValue<string>("stringValue");
|
||||
decimal topLevelDecimal = jObj.GetValue<decimal>("decimalValue");
|
||||
int subLevelInteger = jObj.GetValue<int>("object:IntegerValue");
|
||||
string subLevelString = jObj.GetValue<string>("object:stringValue");
|
||||
|
||||
string notExistingOnTopLevel = jObj.GetValue<string>("fancyValue");
|
||||
string notExistingOnSubLevel = jObj.GetValue<string>("object:fancyValue");
|
||||
int? notExistingLevel = jObj.GetValue<int?>("fancy:int");
|
||||
|
||||
// assert
|
||||
Assert.AreEqual(obj.StringValue, topLevelString);
|
||||
Assert.AreEqual(obj.DecimalValue, topLevelDecimal);
|
||||
Assert.AreEqual(obj.Object.IntegerValue, subLevelInteger);
|
||||
Assert.AreEqual(obj.Object.StringValue, subLevelString);
|
||||
|
||||
Assert.IsNull(notExistingOnTopLevel);
|
||||
Assert.IsNull(notExistingOnSubLevel);
|
||||
Assert.IsNull(notExistingLevel);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ShouldRunJsonTreeWithDefault()
|
||||
{
|
||||
// arrange
|
||||
var obj = new JsonTestClass { StringValue = "Running Json", Object = new JsonTestSubClass { IntegerValue = 4711 } };
|
||||
var jObj = obj.ConvertToJObject();
|
||||
|
||||
// act
|
||||
string topLevelString = jObj.GetValue("stringValue", "Test String");
|
||||
decimal topLevelDecimal = jObj.GetValue("decimalValue", 13.24m);
|
||||
int subLevelInteger = jObj.GetValue("object:IntegerValue", 55);
|
||||
string subLevelString = jObj.GetValue("object:stringValue", "Yeah!");
|
||||
|
||||
string notExistingOnTopLevel = jObj.GetValue("fancyValue", "Party!");
|
||||
string notExistingOnSubLevel = jObj.GetValue("object:fancyValue", "Well Done");
|
||||
int? notExistingLevel = jObj.GetValue<int?>("fancy:int", 13);
|
||||
|
||||
// assert
|
||||
Assert.AreEqual(obj.StringValue, topLevelString);
|
||||
Assert.AreEqual(obj.DecimalValue, topLevelDecimal);
|
||||
Assert.AreEqual(obj.Object.IntegerValue, subLevelInteger);
|
||||
Assert.AreEqual(obj.Object.StringValue, subLevelString);
|
||||
|
||||
Assert.AreEqual("Party!", notExistingOnTopLevel);
|
||||
Assert.AreEqual("Well Done", notExistingOnSubLevel);
|
||||
Assert.AreEqual(13, notExistingLevel);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ShouldReturnNull()
|
||||
{
|
||||
// arrange
|
||||
object obj = null;
|
||||
IEnumerable list = null;
|
||||
JObject jObj = null;
|
||||
|
||||
// act
|
||||
var objTest = obj.ConvertToJObject();
|
||||
var listTest = list.ConvertToJArray();
|
||||
object getTest = jObj.GetValue<object>("Nothing");
|
||||
|
||||
// assert
|
||||
Assert.IsNull(objTest);
|
||||
Assert.IsNull(listTest);
|
||||
Assert.IsNull(getTest);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ShouldHandleSerializationError()
|
||||
{
|
||||
// arrange
|
||||
var obj = new JsonErrorClass();
|
||||
|
||||
// act
|
||||
string json = obj.SerializeJson();
|
||||
|
||||
// assert
|
||||
Assert.AreEqual("{}", json);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,219 @@
|
||||
using System;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace AMWD.Common.Tests.Extensions
|
||||
{
|
||||
[TestClass]
|
||||
public class ReaderWriterLockSlimExtensionsTest
|
||||
{
|
||||
[TestMethod]
|
||||
public void ShouldEnterReadLock()
|
||||
{
|
||||
// arrange
|
||||
var rwLock = new ReaderWriterLockSlim();
|
||||
|
||||
// act
|
||||
using var disposable = rwLock.GetReadLock();
|
||||
|
||||
// assert
|
||||
Assert.IsNotNull(disposable);
|
||||
Assert.IsTrue(rwLock.IsReadLockHeld);
|
||||
Assert.IsFalse(rwLock.IsUpgradeableReadLockHeld);
|
||||
Assert.IsFalse(rwLock.IsWriteLockHeld);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ShouldEnterUpgradeableReadLock()
|
||||
{
|
||||
// arrange
|
||||
var rwLock = new ReaderWriterLockSlim();
|
||||
|
||||
// act
|
||||
using var disposable = rwLock.GetUpgradeableReadLock();
|
||||
|
||||
// assert
|
||||
Assert.IsNotNull(disposable);
|
||||
Assert.IsFalse(rwLock.IsReadLockHeld);
|
||||
Assert.IsTrue(rwLock.IsUpgradeableReadLockHeld);
|
||||
Assert.IsFalse(rwLock.IsWriteLockHeld);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ShouldAllowWriteLockAfterUpgradableReadLock()
|
||||
{
|
||||
// arrange
|
||||
var rwLockUsing = new ReaderWriterLockSlim();
|
||||
var rwLockClassic = new ReaderWriterLockSlim();
|
||||
|
||||
// act
|
||||
using var disposableReadUsing = rwLockUsing.GetUpgradeableReadLock();
|
||||
using (rwLockUsing.GetWriteLock())
|
||||
{
|
||||
// assert
|
||||
Assert.IsTrue(rwLockUsing.IsUpgradeableReadLockHeld);
|
||||
Assert.IsTrue(rwLockUsing.IsWriteLockHeld);
|
||||
}
|
||||
// assert
|
||||
Assert.IsTrue(rwLockUsing.IsUpgradeableReadLockHeld);
|
||||
Assert.IsFalse(rwLockUsing.IsWriteLockHeld);
|
||||
|
||||
// act
|
||||
using (rwLockClassic.GetUpgradeableReadLock())
|
||||
{
|
||||
rwLockClassic.EnterWriteLock();
|
||||
|
||||
// assert
|
||||
Assert.IsTrue(rwLockClassic.IsUpgradeableReadLockHeld);
|
||||
Assert.IsTrue(rwLockClassic.IsWriteLockHeld);
|
||||
}
|
||||
// assert
|
||||
Assert.IsFalse(rwLockClassic.IsUpgradeableReadLockHeld);
|
||||
Assert.IsFalse(rwLockClassic.IsWriteLockHeld);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ShouldEnterWriteLock()
|
||||
{
|
||||
// arrange
|
||||
var rwLock = new ReaderWriterLockSlim();
|
||||
|
||||
// act
|
||||
using var disposable = rwLock.GetWriteLock();
|
||||
|
||||
// assert
|
||||
Assert.IsNotNull(disposable);
|
||||
Assert.IsFalse(rwLock.IsReadLockHeld);
|
||||
Assert.IsFalse(rwLock.IsUpgradeableReadLockHeld);
|
||||
Assert.IsTrue(rwLock.IsWriteLockHeld);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ShouldNotAllowWriteLockAfterReadLock()
|
||||
{
|
||||
// arrange
|
||||
var rwLock = new ReaderWriterLockSlim();
|
||||
|
||||
// act
|
||||
using var disposableRead = rwLock.GetReadLock();
|
||||
try
|
||||
{
|
||||
using var disposaleWrite = rwLock.GetWriteLock();
|
||||
Assert.Fail();
|
||||
}
|
||||
catch (LockRecursionException)
|
||||
{ }
|
||||
|
||||
// assert
|
||||
Assert.IsNotNull(disposableRead);
|
||||
Assert.IsTrue(rwLock.IsReadLockHeld);
|
||||
Assert.IsFalse(rwLock.IsUpgradeableReadLockHeld);
|
||||
Assert.IsFalse(rwLock.IsWriteLockHeld);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ShouldAllowWriteLockAfterUpgradeableReadLock()
|
||||
{
|
||||
// arrange
|
||||
var rwLock = new ReaderWriterLockSlim();
|
||||
|
||||
// act
|
||||
using var disposableRead = rwLock.GetUpgradeableReadLock();
|
||||
using var disposableWrite = rwLock.GetWriteLock();
|
||||
|
||||
// assert
|
||||
Assert.IsNotNull(disposableRead);
|
||||
Assert.IsNotNull(disposableWrite);
|
||||
Assert.IsFalse(rwLock.IsReadLockHeld);
|
||||
Assert.IsTrue(rwLock.IsUpgradeableReadLockHeld);
|
||||
Assert.IsTrue(rwLock.IsWriteLockHeld);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ShouldGetTimeoutOnReadLock()
|
||||
{
|
||||
// arrange
|
||||
var rwLock = new ReaderWriterLockSlim();
|
||||
bool isTimeout = false;
|
||||
|
||||
// act
|
||||
using var disposableRead = rwLock.GetWriteLock();
|
||||
var awaitableTask = Task.Run(() =>
|
||||
{
|
||||
try
|
||||
{
|
||||
using var disposableRead = rwLock.GetReadLock(10);
|
||||
Assert.Fail();
|
||||
}
|
||||
catch (TimeoutException)
|
||||
{
|
||||
isTimeout = true;
|
||||
}
|
||||
catch (Exception)
|
||||
{ /* keep it quiet */ }
|
||||
});
|
||||
awaitableTask.Wait();
|
||||
|
||||
// assert
|
||||
Assert.IsTrue(isTimeout);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ShouldGetTimeoutOnUpgradeableReadLock()
|
||||
{
|
||||
// arrange
|
||||
var rwLock = new ReaderWriterLockSlim();
|
||||
bool isTimeout = false;
|
||||
|
||||
// act
|
||||
using var disposableRead = rwLock.GetWriteLock();
|
||||
var awaitableTask = Task.Run(() =>
|
||||
{
|
||||
try
|
||||
{
|
||||
using var disposableRead = rwLock.GetUpgradeableReadLock(10);
|
||||
Assert.Fail();
|
||||
}
|
||||
catch (TimeoutException)
|
||||
{
|
||||
isTimeout = true;
|
||||
}
|
||||
catch (Exception)
|
||||
{ /* keep it quiet */ }
|
||||
});
|
||||
awaitableTask.Wait();
|
||||
|
||||
// assert
|
||||
Assert.IsTrue(isTimeout);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ShouldGetTimeoutOnWriteLock()
|
||||
{
|
||||
// arrange
|
||||
var rwLock = new ReaderWriterLockSlim();
|
||||
bool isTimeout = false;
|
||||
|
||||
// act
|
||||
using var disposableRead = rwLock.GetReadLock();
|
||||
var awaitableTask = Task.Run(() =>
|
||||
{
|
||||
try
|
||||
{
|
||||
using var disposableRead = rwLock.GetWriteLock(10);
|
||||
Assert.Fail();
|
||||
}
|
||||
catch (TimeoutException)
|
||||
{
|
||||
isTimeout = true;
|
||||
}
|
||||
catch (Exception)
|
||||
{ /* keep it quiet */ }
|
||||
});
|
||||
awaitableTask.Wait();
|
||||
|
||||
// assert
|
||||
Assert.IsTrue(isTimeout);
|
||||
}
|
||||
}
|
||||
}
|
||||
137
test/AMWD.Common.Tests/Extensions/StreamExtensionsTest.cs
Normal file
137
test/AMWD.Common.Tests/Extensions/StreamExtensionsTest.cs
Normal file
@@ -0,0 +1,137 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace AMWD.Common.Tests.Extensions
|
||||
{
|
||||
[TestClass]
|
||||
public class StreamExtensionsTest
|
||||
{
|
||||
[TestMethod]
|
||||
public void ShouldReadLineFromStreamSynchronous()
|
||||
{
|
||||
// arrange
|
||||
var sb = new StringBuilder();
|
||||
sb.AppendLine("First Line");
|
||||
sb.AppendLine("Second Line");
|
||||
byte[] buffer = Encoding.UTF8.GetBytes(sb.ToString().Trim());
|
||||
var stream = new MemoryStream(buffer);
|
||||
|
||||
// act
|
||||
string line = stream.ReadLine();
|
||||
|
||||
// assert
|
||||
Assert.AreEqual("First Line", line);
|
||||
|
||||
stream.Dispose();
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ShouldReadUntilEndAsLineSynchronous()
|
||||
{
|
||||
// arrange
|
||||
byte[] buffer = Encoding.UTF8.GetBytes("Single Line");
|
||||
var stream = new MemoryStream(buffer);
|
||||
|
||||
// act
|
||||
string line = stream.ReadLine();
|
||||
|
||||
// assert
|
||||
Assert.AreEqual("Single Line", line);
|
||||
|
||||
stream.Dispose();
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ShouldReturnNullWhenNotReadableSynchronous()
|
||||
{
|
||||
// arrange
|
||||
var stream = new WriteOnlyStream();
|
||||
|
||||
// act
|
||||
string line = stream.ReadLine();
|
||||
|
||||
// assert
|
||||
Assert.IsNull(line);
|
||||
|
||||
stream.Dispose();
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public async Task ShouldReadLineFromStreamAsynchronous()
|
||||
{
|
||||
// arrange
|
||||
var sb = new StringBuilder();
|
||||
sb.AppendLine("First Line");
|
||||
sb.AppendLine("Second Line");
|
||||
byte[] buffer = Encoding.UTF8.GetBytes(sb.ToString().Trim());
|
||||
var stream = new MemoryStream(buffer);
|
||||
|
||||
// act
|
||||
string line = await stream.ReadLineAsync();
|
||||
|
||||
// assert
|
||||
Assert.AreEqual("First Line", line);
|
||||
|
||||
stream.Dispose();
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public async Task ShouldReadUntilEndAsLineAsynchronous()
|
||||
{
|
||||
// arrange
|
||||
byte[] buffer = Encoding.UTF8.GetBytes("Single Line");
|
||||
var stream = new MemoryStream(buffer);
|
||||
|
||||
// act
|
||||
string line = await stream.ReadLineAsync();
|
||||
|
||||
// assert
|
||||
Assert.AreEqual("Single Line", line);
|
||||
|
||||
stream.Dispose();
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public async Task ShouldReturnNullWhenNotReadableAsynchronous()
|
||||
{
|
||||
// arrange
|
||||
var stream = new WriteOnlyStream();
|
||||
|
||||
// act
|
||||
string line = await stream.ReadLineAsync();
|
||||
|
||||
// assert
|
||||
Assert.IsNull(line);
|
||||
|
||||
stream.Dispose();
|
||||
}
|
||||
|
||||
private class WriteOnlyStream : Stream
|
||||
{
|
||||
public override bool CanRead => false;
|
||||
|
||||
public override bool CanSeek => false;
|
||||
|
||||
public override bool CanWrite => true;
|
||||
|
||||
public override long Length => 0;
|
||||
|
||||
public override long Position { get => 0; set => throw new NotImplementedException(); }
|
||||
|
||||
public override void Flush()
|
||||
{ }
|
||||
|
||||
public override int Read(byte[] buffer, int offset, int count) => 0;
|
||||
|
||||
public override long Seek(long offset, SeekOrigin origin) => 0;
|
||||
|
||||
public override void SetLength(long value)
|
||||
{ }
|
||||
|
||||
public override void Write(byte[] buffer, int offset, int count)
|
||||
{ }
|
||||
}
|
||||
}
|
||||
}
|
||||
351
test/AMWD.Common.Tests/Extensions/StringExtensionsTest.cs
Normal file
351
test/AMWD.Common.Tests/Extensions/StringExtensionsTest.cs
Normal file
@@ -0,0 +1,351 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Text;
|
||||
|
||||
namespace AMWD.Common.Tests.Extensions
|
||||
{
|
||||
[TestClass]
|
||||
public class StringExtensionsTest
|
||||
{
|
||||
[TestMethod]
|
||||
public void ShouldReturnEmptyList()
|
||||
{
|
||||
// arrange
|
||||
string hex = "";
|
||||
|
||||
// act
|
||||
var bytes = hex.HexToBytes();
|
||||
|
||||
// assert
|
||||
Assert.IsNotNull(bytes);
|
||||
Assert.IsFalse(bytes.Any());
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ShouldReturnEmptyListWhenInvalid()
|
||||
{
|
||||
// arrange
|
||||
string hex1 = "aff";
|
||||
string hex2 = "de:ad:be:e";
|
||||
string hex3 = "hell";
|
||||
|
||||
// act
|
||||
var bytes1 = hex1.HexToBytes();
|
||||
var bytes2 = hex2.HexToBytes(":");
|
||||
var bytes3 = hex3.HexToBytes();
|
||||
|
||||
// assert
|
||||
Assert.IsNotNull(bytes1);
|
||||
Assert.IsFalse(bytes1.Any());
|
||||
|
||||
Assert.IsNotNull(bytes2);
|
||||
Assert.IsFalse(bytes2.Any());
|
||||
|
||||
Assert.IsNotNull(bytes3);
|
||||
Assert.IsFalse(bytes3.Any());
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ShouldConvertHexToBytes()
|
||||
{
|
||||
// arrange
|
||||
string hex = "deadbeef";
|
||||
|
||||
// act
|
||||
var bytes = hex.HexToBytes();
|
||||
|
||||
// assert
|
||||
Assert.IsNotNull(bytes);
|
||||
Assert.AreEqual(4, bytes.Count());
|
||||
Assert.AreEqual(0xde, bytes.ElementAt(0));
|
||||
Assert.AreEqual(0xad, bytes.ElementAt(1));
|
||||
Assert.AreEqual(0xbe, bytes.ElementAt(2));
|
||||
Assert.AreEqual(0xef, bytes.ElementAt(3));
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ShouldConvertHexToBytesWithDelimiter()
|
||||
{
|
||||
// arrange
|
||||
string hex = "af:fe";
|
||||
|
||||
// act
|
||||
var bytes = hex.HexToBytes(":");
|
||||
|
||||
// assert
|
||||
Assert.IsNotNull(bytes);
|
||||
Assert.AreEqual(2, bytes.Count());
|
||||
Assert.AreEqual(0xaf, bytes.ElementAt(0));
|
||||
Assert.AreEqual(0xfe, bytes.ElementAt(1));
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ShouldReturnNullWhenInvalid()
|
||||
{
|
||||
// arrange
|
||||
byte[] bytes1 = null;
|
||||
byte[] bytes2 = [];
|
||||
|
||||
// act
|
||||
string hex1 = bytes1.BytesToHex();
|
||||
string hex2 = bytes2.BytesToHex();
|
||||
|
||||
// assert
|
||||
Assert.IsNull(hex1);
|
||||
Assert.IsNull(hex2);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ShouldReturnHexString()
|
||||
{
|
||||
// arrange
|
||||
byte[] bytes = [0xaf, 0xfe];
|
||||
|
||||
// act
|
||||
string hex = bytes.BytesToHex();
|
||||
|
||||
// assert
|
||||
Assert.IsNotNull(hex);
|
||||
Assert.AreEqual("affe", hex);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ShouldReturnHexStringWithDelimiter()
|
||||
{
|
||||
// arrange
|
||||
byte[] bytes = [0xde, 0xad, 0xbe, 0xef];
|
||||
|
||||
// act
|
||||
string hex = bytes.BytesToHex("_");
|
||||
|
||||
// assert
|
||||
Assert.IsNotNull(hex);
|
||||
Assert.AreEqual("de_ad_be_ef", hex);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ShouldEncodeStringToHex()
|
||||
{
|
||||
// arrange
|
||||
string plain = "Hello";
|
||||
|
||||
// act
|
||||
string hex1 = plain.HexEncode();
|
||||
string hex2 = plain.HexEncode(Encoding.Default);
|
||||
|
||||
// assert
|
||||
Assert.AreEqual("48656c6c6f", hex1);
|
||||
Assert.AreEqual("48656c6c6f", hex2);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ShouldDecodeStringFromHex()
|
||||
{
|
||||
// arrange
|
||||
string hex = "48656c6c6f";
|
||||
|
||||
// act
|
||||
string plain1 = hex.HexDecode();
|
||||
string plain2 = hex.HexDecode(Encoding.Default);
|
||||
|
||||
// assert
|
||||
Assert.AreEqual("Hello", plain1);
|
||||
Assert.AreEqual("Hello", plain2);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ShouldEncodeStringToBase64()
|
||||
{
|
||||
// arrange
|
||||
string plain = "Hello";
|
||||
|
||||
// act
|
||||
string base641 = plain.Base64Encode();
|
||||
string base642 = plain.Base64Encode(Encoding.Default);
|
||||
|
||||
// assert
|
||||
Assert.AreEqual("SGVsbG8=", base641);
|
||||
Assert.AreEqual("SGVsbG8=", base642);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ShouldDecodeStringFromBase64()
|
||||
{
|
||||
// arrange
|
||||
string base64 = "SGVsbG8=";
|
||||
|
||||
// act
|
||||
string plain1 = base64.Base64Decode();
|
||||
string plain2 = base64.Base64Decode(Encoding.Default);
|
||||
|
||||
// assert
|
||||
Assert.AreEqual("Hello", plain1);
|
||||
Assert.AreEqual("Hello", plain2);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ShouldReplaceStart()
|
||||
{
|
||||
// arrange
|
||||
string str = "Hello World!";
|
||||
|
||||
// act
|
||||
string test1 = str.ReplaceStart("Hello", "Bye");
|
||||
string test2 = str.ReplaceStart("World!", "Mars?");
|
||||
|
||||
// assert
|
||||
Assert.AreNotEqual(str, test1);
|
||||
Assert.AreEqual("Bye World!", test1);
|
||||
|
||||
Assert.AreEqual(str, test2);
|
||||
Assert.AreNotEqual("Hello Mars?", test2);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ShouldReplaceEnd()
|
||||
{
|
||||
// arrange
|
||||
string str = "Hello World!";
|
||||
|
||||
// act
|
||||
string test1 = str.ReplaceEnd("Hello", "Bye");
|
||||
string test2 = str.ReplaceEnd("World!", "Mars?");
|
||||
|
||||
// assert
|
||||
Assert.AreEqual(str, test1);
|
||||
Assert.AreNotEqual("Bye World!", test1);
|
||||
|
||||
Assert.AreNotEqual(str, test2);
|
||||
Assert.AreEqual("Hello Mars?", test2);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ShouldParseDecimal()
|
||||
{
|
||||
// arrange
|
||||
decimal number = 1234.56m;
|
||||
string stringNumberEn = "1234.56";
|
||||
string stringNumberDe = "1234,56";
|
||||
|
||||
// act
|
||||
decimal numberEn = stringNumberEn.ParseDecimal();
|
||||
decimal numberDe = stringNumberDe.ParseDecimal();
|
||||
|
||||
// assert
|
||||
Assert.AreEqual(number, numberEn);
|
||||
Assert.AreEqual(number, numberDe);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ShouldParseDecimalWithThousandsSeparator()
|
||||
{
|
||||
// arrange
|
||||
decimal number = 1234.56m;
|
||||
string stringNumberEn = "1,234.56";
|
||||
string stringNumberDe = "1.234,56";
|
||||
|
||||
// act
|
||||
decimal numberEn = stringNumberEn.ParseDecimal();
|
||||
decimal numberDe = stringNumberDe.ParseDecimal();
|
||||
|
||||
// assert
|
||||
Assert.AreEqual(number, numberEn);
|
||||
Assert.AreEqual(number, numberDe);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ShouldValidateEmailAddressWithoutRecordCheck()
|
||||
{
|
||||
// arrange
|
||||
string validEmailWithoutTag = "test@gmail.com";
|
||||
string validEmailWithTag = "test+tag@not.exists";
|
||||
string invalidEmailWithoutTag = "<Test Account> test@gmail.com";
|
||||
string invalidEmailWithTag = "<Test Account> test+tag@not.exists";
|
||||
string nullStr = null;
|
||||
|
||||
// act
|
||||
bool validWithoutTag = validEmailWithoutTag.IsValidEmailAddress(checkForDnsRecord: false);
|
||||
bool validWithTag = validEmailWithTag.IsValidEmailAddress(checkForDnsRecord: false);
|
||||
bool invalidWithoutTag = !invalidEmailWithoutTag.IsValidEmailAddress(checkForDnsRecord: false);
|
||||
bool invalidWithTag = !invalidEmailWithTag.IsValidEmailAddress(checkForDnsRecord: false);
|
||||
bool nullTest = nullStr.IsValidEmailAddress(checkForDnsRecord: false);
|
||||
|
||||
// assert
|
||||
Assert.IsTrue(validWithoutTag);
|
||||
Assert.IsTrue(validWithTag);
|
||||
Assert.IsTrue(invalidWithoutTag);
|
||||
Assert.IsTrue(invalidWithTag);
|
||||
Assert.IsFalse(nullTest);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ShouldValidateEmailAddressWithRecordCheck()
|
||||
{
|
||||
// arrange
|
||||
string validEmail = "test@gmail.com";
|
||||
string invalidEmail = "test@not.exists";
|
||||
|
||||
// act
|
||||
bool valid = validEmail.IsValidEmailAddress(checkForDnsRecord: true);
|
||||
bool invalid = !invalidEmail.IsValidEmailAddress(checkForDnsRecord: true);
|
||||
|
||||
// assert
|
||||
Assert.IsTrue(valid);
|
||||
Assert.IsTrue(invalid);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ShouldValidateEmailAddressWithRecordCheckDefinedNameservers()
|
||||
{
|
||||
// arrange
|
||||
string validEmail = "test@gmail.com";
|
||||
string invalidEmail = "test@not.exists";
|
||||
var nameserver = new IPEndPoint(IPAddress.Parse("1.1.1.1"), 53);
|
||||
|
||||
// act
|
||||
bool valid = validEmail.IsValidEmailAddress(new[] { nameserver });
|
||||
bool invalid = !invalidEmail.IsValidEmailAddress(new[] { nameserver });
|
||||
|
||||
// assert
|
||||
Assert.IsTrue(valid);
|
||||
Assert.IsTrue(invalid);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ShouldWorkWithNullOrEmptyString()
|
||||
{
|
||||
// arrange
|
||||
string nullStr = null;
|
||||
string emptyStr = "";
|
||||
|
||||
// act
|
||||
string hexEncodeNull = nullStr.HexEncode();
|
||||
string hexEncodeEmpty = emptyStr.HexEncode();
|
||||
string hexDecodeNull = nullStr.HexDecode();
|
||||
string hexDecodeEmpty = emptyStr.HexDecode();
|
||||
|
||||
// assert
|
||||
Assert.IsNull(hexEncodeNull);
|
||||
Assert.AreEqual("", hexEncodeEmpty);
|
||||
Assert.IsNull(hexDecodeNull);
|
||||
Assert.AreEqual("", hexDecodeEmpty);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ShouldAddCustomLineTermination()
|
||||
{
|
||||
// arrange
|
||||
string value = "abc";
|
||||
var sb = new StringBuilder();
|
||||
|
||||
// act
|
||||
sb.AppendLine(value, "\r");
|
||||
sb.AppendLine(value, "\r");
|
||||
|
||||
// assert
|
||||
Assert.AreEqual($"{value}\r{value}\r", sb.ToString());
|
||||
Assert.IsFalse(sb.ToString().Contains('\n'));
|
||||
}
|
||||
}
|
||||
}
|
||||
370
test/AMWD.Common.Tests/Logging/FileLoggerTest.cs
Normal file
370
test/AMWD.Common.Tests/Logging/FileLoggerTest.cs
Normal file
@@ -0,0 +1,370 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using AMWD.Common.Logging;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Moq;
|
||||
|
||||
namespace AMWD.Common.Tests.Logging
|
||||
{
|
||||
[TestClass]
|
||||
public class FileLoggerTest
|
||||
{
|
||||
private Mock<StreamWriter> _streamWriterMock;
|
||||
|
||||
private List<string> _lines;
|
||||
|
||||
[TestInitialize]
|
||||
public void Initialize()
|
||||
{
|
||||
_lines = [];
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ShouldCreateInstance()
|
||||
{
|
||||
// arrange
|
||||
string path = Path.GetTempFileName();
|
||||
|
||||
try
|
||||
{
|
||||
// act
|
||||
using var logger = new FileLogger(path);
|
||||
|
||||
// assert
|
||||
Assert.IsNotNull(logger);
|
||||
Assert.IsTrue(File.Exists(path));
|
||||
|
||||
Assert.AreEqual(path, logger.FileName);
|
||||
Assert.AreEqual(LogLevel.Trace, logger.MinLevel);
|
||||
Assert.IsNull(logger.Name);
|
||||
Assert.IsNull(logger.ParentLogger);
|
||||
Assert.IsNull(logger.TimestampFormat);
|
||||
Assert.IsNull(logger.ScopeProvider);
|
||||
}
|
||||
finally
|
||||
{
|
||||
File.Delete(path);
|
||||
}
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ShouldCreateInstanceAsNamedInstance()
|
||||
{
|
||||
// arrange
|
||||
string name = "NamedInstance";
|
||||
string path = Path.GetTempFileName();
|
||||
|
||||
try
|
||||
{
|
||||
// act
|
||||
using var logger = new FileLogger(path, name);
|
||||
|
||||
// assert
|
||||
Assert.IsNotNull(logger);
|
||||
Assert.IsTrue(File.Exists(path));
|
||||
|
||||
Assert.AreEqual(path, logger.FileName);
|
||||
Assert.AreEqual(LogLevel.Trace, logger.MinLevel);
|
||||
Assert.AreEqual(name, logger.Name);
|
||||
|
||||
Assert.IsNull(logger.ParentLogger);
|
||||
Assert.IsNull(logger.TimestampFormat);
|
||||
Assert.IsNull(logger.ScopeProvider);
|
||||
}
|
||||
finally
|
||||
{
|
||||
File.Delete(path);
|
||||
}
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[ExpectedException(typeof(ObjectDisposedException))]
|
||||
public void ShouldThrowDisposedOnIsEnabled()
|
||||
{
|
||||
// arrange
|
||||
var logger = GetFileLogger();
|
||||
|
||||
// act
|
||||
logger.Dispose();
|
||||
logger.IsEnabled(LogLevel.Error);
|
||||
|
||||
// assert - ObjectDisposedException
|
||||
Assert.Fail();
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[ExpectedException(typeof(ObjectDisposedException))]
|
||||
public void ShouldThrowDisposedOnLog()
|
||||
{
|
||||
// arrange
|
||||
var logger = GetFileLogger();
|
||||
|
||||
// act
|
||||
logger.Dispose();
|
||||
logger.Log(LogLevel.None, "Some Message");
|
||||
|
||||
// assert - ObjectDisposedException
|
||||
Assert.Fail();
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[ExpectedException(typeof(ObjectDisposedException))]
|
||||
public void ShouldThrowDisposedOnBeginScope()
|
||||
{
|
||||
// arrange
|
||||
var logger = GetFileLogger();
|
||||
|
||||
// act
|
||||
logger.Dispose();
|
||||
logger.BeginScope("foo");
|
||||
|
||||
// assert - ObjectDisposedException
|
||||
Assert.Fail();
|
||||
}
|
||||
|
||||
[DataTestMethod]
|
||||
[DataRow(LogLevel.Trace, false)]
|
||||
[DataRow(LogLevel.Debug, false)]
|
||||
[DataRow(LogLevel.Information, false)]
|
||||
[DataRow(LogLevel.Warning, true)]
|
||||
[DataRow(LogLevel.Error, true)]
|
||||
[DataRow(LogLevel.Critical, true)]
|
||||
[DataRow(LogLevel.None, true)]
|
||||
public void ShouldReturnIsEnabled(LogLevel logLevel, bool expectedResult)
|
||||
{
|
||||
// arrange
|
||||
using var logger = GetFileLogger();
|
||||
logger.MinLevel = LogLevel.Warning;
|
||||
|
||||
// act
|
||||
bool result = logger.IsEnabled(logLevel);
|
||||
|
||||
// assert
|
||||
Assert.AreEqual(expectedResult, result);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ShouldLogMessage()
|
||||
{
|
||||
// arrange
|
||||
using var logger = GetFileLogger();
|
||||
|
||||
// act
|
||||
logger.Log(LogLevel.Information, "Test Message");
|
||||
SpinWait.SpinUntil(() => _lines.Count == 1);
|
||||
|
||||
// assert
|
||||
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();
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ShouldLogAllLevels()
|
||||
{
|
||||
// arrange
|
||||
using var logger = GetFileLogger();
|
||||
|
||||
// act
|
||||
foreach (LogLevel level in Enum.GetValues<LogLevel>())
|
||||
logger.Log(level, "Test Message");
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ShouldLogOnlyAboveMinLevel()
|
||||
{
|
||||
// arrange
|
||||
using var logger = GetFileLogger();
|
||||
logger.MinLevel = LogLevel.Error;
|
||||
|
||||
// act
|
||||
foreach (LogLevel level in Enum.GetValues<LogLevel>())
|
||||
logger.Log(level, "Test Message");
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ShouldLogWithLocalTimestamp()
|
||||
{
|
||||
// arrange
|
||||
using var logger = GetFileLogger();
|
||||
logger.UseUtcTimestamp = false;
|
||||
logger.TimestampFormat = "yyyy-MM-dd HH:mm";
|
||||
|
||||
// act
|
||||
logger.LogWarning("Some Warning");
|
||||
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();
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ShouldLogWithUtcTimestamp()
|
||||
{
|
||||
// arrange
|
||||
using var logger = GetFileLogger();
|
||||
logger.UseUtcTimestamp = true;
|
||||
logger.TimestampFormat = "yyyy-MM-dd HH:mm";
|
||||
|
||||
// act
|
||||
logger.LogWarning("Some Warning");
|
||||
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();
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ShouldUseParentLogger()
|
||||
{
|
||||
// arrange
|
||||
string file = Path.GetTempFileName();
|
||||
try
|
||||
{
|
||||
using var parent = GetFileLogger();
|
||||
parent.UseUtcTimestamp = false;
|
||||
parent.TimestampFormat = "yyyy-MM-dd HH:mm";
|
||||
|
||||
using var logger = new FileLogger(file, "NamedInstance", parent);
|
||||
|
||||
// act
|
||||
logger.LogWarning("Some Warning");
|
||||
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();
|
||||
|
||||
Assert.AreEqual(0, new FileInfo(file).Length);
|
||||
}
|
||||
finally
|
||||
{
|
||||
File.Delete(file);
|
||||
}
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ShouldUseScopeProvider()
|
||||
{
|
||||
// arrange
|
||||
var scopeProvider = new Mock<IExternalScopeProvider>();
|
||||
using var logger = GetFileLogger("NamedInstance", scopeProvider.Object);
|
||||
|
||||
// act
|
||||
using (var scope = logger.BeginScope("scope"))
|
||||
{
|
||||
logger.LogError("Test");
|
||||
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();
|
||||
|
||||
scopeProvider.Verify(sp => sp.Push("scope"), Times.Once);
|
||||
scopeProvider.Verify(sp => sp.ForEachScope(It.IsAny<Action<object, It.IsAnyType>>(), It.IsAny<It.IsAnyType>()), Times.Once);
|
||||
scopeProvider.VerifyNoOtherCalls();
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ShouldLogException()
|
||||
{
|
||||
// arrange
|
||||
using var logger = GetFileLogger();
|
||||
|
||||
// act
|
||||
logger.LogCritical(new Exception("TestException"), "");
|
||||
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();
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ShouldLogExceptionWithMessage()
|
||||
{
|
||||
// arrange
|
||||
using var logger = GetFileLogger();
|
||||
|
||||
// act
|
||||
logger.LogCritical(new Exception("TestException"), "Bad things happen...");
|
||||
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();
|
||||
}
|
||||
|
||||
private FileLogger GetFileLogger(string name = null, IExternalScopeProvider scopeProvider = null)
|
||||
{
|
||||
string tmpFilePath = Path.GetTempFileName();
|
||||
try
|
||||
{
|
||||
_streamWriterMock = new Mock<StreamWriter>(Stream.Null);
|
||||
_streamWriterMock
|
||||
.Setup(sw => sw.WriteLineAsync(It.IsAny<string>()))
|
||||
.Callback<string>(line => _lines.Add(line))
|
||||
.Returns(Task.CompletedTask);
|
||||
|
||||
FileLogger fileLogger;
|
||||
if (name == null || scopeProvider == null)
|
||||
{
|
||||
fileLogger = new FileLogger(tmpFilePath);
|
||||
}
|
||||
else
|
||||
{
|
||||
fileLogger = new FileLogger(tmpFilePath, name, scopeProvider);
|
||||
}
|
||||
|
||||
var fieldInfo = fileLogger.GetType().GetField("_fileWriter", BindingFlags.NonPublic | BindingFlags.Instance);
|
||||
(fieldInfo.GetValue(fileLogger) as StreamWriter).Dispose();
|
||||
fieldInfo.SetValue(fileLogger, _streamWriterMock.Object);
|
||||
|
||||
return fileLogger;
|
||||
}
|
||||
finally
|
||||
{
|
||||
File.Delete(tmpFilePath);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
353
test/AMWD.Common.Tests/Packing/Ar/ArReaderTest.cs
Normal file
353
test/AMWD.Common.Tests/Packing/Ar/ArReaderTest.cs
Normal file
@@ -0,0 +1,353 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using AMWD.Common.Packing.Ar;
|
||||
|
||||
namespace AMWD.Common.Tests.Packing.Ar
|
||||
{
|
||||
[TestClass]
|
||||
public class ArReaderTest
|
||||
{
|
||||
private readonly DateTime _fixedDateTime = new(2023, 03, 01, 10, 20, 30, 0, DateTimeKind.Utc);
|
||||
|
||||
private Dictionary<string, ArFileInfo> _files;
|
||||
|
||||
private MemoryStream _inStream;
|
||||
|
||||
[TestInitialize]
|
||||
public void Initialize()
|
||||
{
|
||||
_files = new Dictionary<string, ArFileInfo>
|
||||
{
|
||||
{
|
||||
"abcd.tmp",
|
||||
new ArFileInfo
|
||||
{
|
||||
FileName = "abcd.tmp",
|
||||
FileSize = 14,
|
||||
GroupId = 456,
|
||||
Mode = 33188,
|
||||
ModifyTime = _fixedDateTime,
|
||||
UserId = 123
|
||||
}
|
||||
},
|
||||
{
|
||||
"efgh.tmp",
|
||||
new ArFileInfo
|
||||
{
|
||||
FileName = "efgh.tmp",
|
||||
FileSize = 14,
|
||||
GroupId = 456,
|
||||
Mode = 33188,
|
||||
ModifyTime = _fixedDateTime,
|
||||
UserId = 123
|
||||
}
|
||||
},
|
||||
{
|
||||
"ijkl.tmp",
|
||||
new ArFileInfo
|
||||
{
|
||||
FileName = "ijkl.tmp",
|
||||
FileSize = 13,
|
||||
GroupId = 456,
|
||||
Mode = 33188,
|
||||
ModifyTime = _fixedDateTime,
|
||||
UserId = 123
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
_inStream = new MemoryStream();
|
||||
_inStream.Write(Encoding.ASCII.GetBytes("!<arch>\n"));
|
||||
|
||||
foreach (var file in _files)
|
||||
{
|
||||
int unixSeconds = (int)file.Value.ModifyTime.Subtract(DateTime.UnixEpoch).TotalSeconds;
|
||||
|
||||
_inStream.Write(Encoding.ASCII.GetBytes($"{file.Key,-16}{unixSeconds,-12}123 456 100644 {file.Value.FileSize,-10}`\n"));
|
||||
_inStream.Write(Encoding.UTF8.GetBytes(new string('a', (int)file.Value.FileSize)));
|
||||
if (file.Value.FileSize % 2 != 0)
|
||||
_inStream.Write(Encoding.ASCII.GetBytes("\n"));
|
||||
}
|
||||
|
||||
_inStream.Seek(0, SeekOrigin.Begin);
|
||||
}
|
||||
|
||||
[TestCleanup]
|
||||
public void Cleanup()
|
||||
{
|
||||
_inStream.Dispose();
|
||||
_inStream = null;
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ShouldInitializeArchive()
|
||||
{
|
||||
// Arrange
|
||||
_inStream.Dispose();
|
||||
_inStream = new MemoryStream();
|
||||
_inStream.Write(Encoding.ASCII.GetBytes("!<arch>\n"));
|
||||
_inStream.Seek(0, SeekOrigin.Begin);
|
||||
|
||||
// Act
|
||||
var reader = new ArReader(_inStream);
|
||||
|
||||
// Assert
|
||||
Assert.IsNotNull(reader);
|
||||
Assert.IsFalse(reader.GetFileList().Any());
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ShouldInitializeWithFiles()
|
||||
{
|
||||
// Arrange
|
||||
|
||||
// Act
|
||||
var reader = new ArReader(_inStream);
|
||||
|
||||
// Assert
|
||||
Assert.IsNotNull(reader);
|
||||
Assert.IsTrue(reader.GetFileList().Any());
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ShouldListFileNames()
|
||||
{
|
||||
// Arrange
|
||||
var reader = new ArReader(_inStream);
|
||||
|
||||
// Act
|
||||
var fileList = reader.GetFileList().ToList();
|
||||
|
||||
// Assert
|
||||
Assert.IsNotNull(reader);
|
||||
Assert.AreEqual(_files.Count, fileList.Count);
|
||||
|
||||
foreach (string name in _files.Keys)
|
||||
Assert.IsTrue(fileList.Contains(name));
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ShouldReturnValidFileInfo()
|
||||
{
|
||||
// Arrange
|
||||
var infos = new List<ArFileInfo>();
|
||||
var reader = new ArReader(_inStream);
|
||||
|
||||
// Act
|
||||
foreach (string name in _files.Keys)
|
||||
infos.Add(reader.GetFileInfo(name));
|
||||
|
||||
// Assert
|
||||
Assert.IsNotNull(reader);
|
||||
Assert.AreEqual(_files.Count, infos.Count);
|
||||
|
||||
foreach (var expected in _files.Values)
|
||||
{
|
||||
var actual = infos.Single(fi => fi.FileName == expected.FileName);
|
||||
|
||||
Assert.AreEqual(expected.FileName, actual.FileName);
|
||||
Assert.AreEqual(expected.FileSize, actual.FileSize);
|
||||
Assert.AreEqual(expected.GroupId, actual.GroupId);
|
||||
Assert.AreEqual(expected.Mode, actual.Mode);
|
||||
Assert.AreEqual(expected.ModifyTime, actual.ModifyTime);
|
||||
Assert.AreEqual(expected.UserId, actual.UserId);
|
||||
}
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ShouldReturnValidFileContent()
|
||||
{
|
||||
// Arrange
|
||||
var contents = new Dictionary<string, string>();
|
||||
var reader = new ArReader(_inStream);
|
||||
|
||||
// Act
|
||||
foreach (string name in _files.Keys)
|
||||
{
|
||||
using var ms = new MemoryStream();
|
||||
reader.ReadFile(name, ms);
|
||||
ms.Seek(0, SeekOrigin.Begin);
|
||||
|
||||
contents.Add(name, Encoding.UTF8.GetString(ms.ToArray()));
|
||||
}
|
||||
|
||||
// Assert
|
||||
Assert.IsNotNull(reader);
|
||||
Assert.AreEqual(_files.Count, contents.Count);
|
||||
|
||||
foreach (var expected in _files.Values)
|
||||
{
|
||||
string content = contents[expected.FileName];
|
||||
|
||||
if (expected.FileSize % 2 != 0)
|
||||
Assert.AreEqual(13, content.Length);
|
||||
else
|
||||
Assert.AreEqual(14, content.Length);
|
||||
|
||||
Assert.AreEqual(new string('a', (int)expected.FileSize), content);
|
||||
}
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ShouldWriteFileToDisk()
|
||||
{
|
||||
// Arrange
|
||||
string tmpFile = Path.GetTempFileName();
|
||||
var reader = new ArReader(_inStream);
|
||||
|
||||
try
|
||||
{
|
||||
// Act
|
||||
using var ms = new MemoryStream();
|
||||
|
||||
reader.ReadFile("abcd.tmp", ms);
|
||||
reader.ReadFile("abcd.tmp", tmpFile);
|
||||
|
||||
// Assert
|
||||
CollectionAssert.AreEqual(ms.ToArray(), File.ReadAllBytes(tmpFile));
|
||||
}
|
||||
finally
|
||||
{
|
||||
File.Delete(tmpFile);
|
||||
}
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[ExpectedException(typeof(ArgumentException))]
|
||||
public void ShouldThrowExceptionOnMissingRead()
|
||||
{
|
||||
// Arrange
|
||||
using var stream = new OverrideStream();
|
||||
stream.CanReadOR = false;
|
||||
stream.CanSeekOR = true;
|
||||
stream.CanWriteOR = true;
|
||||
|
||||
// Act
|
||||
var reader = new ArReader(stream);
|
||||
|
||||
// Assert - ArgumentException
|
||||
Assert.Fail();
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[ExpectedException(typeof(ArgumentException))]
|
||||
public void ShouldThrowExceptionOnMissingSeek()
|
||||
{
|
||||
// Arrange
|
||||
using var stream = new OverrideStream();
|
||||
stream.CanReadOR = true;
|
||||
stream.CanSeekOR = false;
|
||||
stream.CanWriteOR = true;
|
||||
|
||||
// Act
|
||||
var reader = new ArReader(stream);
|
||||
|
||||
// Assert - ArgumentException
|
||||
Assert.Fail();
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[ExpectedException(typeof(ArgumentException))]
|
||||
public void ShouldThrowExceptionOnMissingWrite()
|
||||
{
|
||||
// Arrange
|
||||
using var stream = new OverrideStream();
|
||||
stream.CanReadOR = true;
|
||||
stream.CanSeekOR = true;
|
||||
stream.CanWriteOR = false;
|
||||
|
||||
var reader = new ArReader(_inStream);
|
||||
|
||||
// Act
|
||||
reader.ReadFile("abcd.tmp", stream);
|
||||
|
||||
// Assert - ArgumentException
|
||||
Assert.Fail();
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[ExpectedException(typeof(FormatException))]
|
||||
public void ShouldThrowExceptionOnInvalidArchive()
|
||||
{
|
||||
// Arrange
|
||||
_inStream.Seek(8, SeekOrigin.Begin);
|
||||
|
||||
// Act
|
||||
_ = new ArReader(_inStream);
|
||||
|
||||
// Assert - FormatException
|
||||
Assert.Fail();
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[ExpectedException(typeof(FormatException))]
|
||||
public void ShouldThrowExceptionOnInvalidMagic()
|
||||
{
|
||||
// Arrange
|
||||
_inStream.Seek(0, SeekOrigin.End);
|
||||
_inStream.Write(Encoding.ASCII.GetBytes($"{"foo.bar",-16}{"123456789",-12}123 456 100644 {"0",-10}´\n"));
|
||||
_inStream.Seek(0, SeekOrigin.Begin);
|
||||
|
||||
// Act
|
||||
_ = new ArReader(_inStream);
|
||||
|
||||
// Assert - FormatException
|
||||
Assert.Fail();
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ShouldWriteNothingToStreamForMissingFile()
|
||||
{
|
||||
// Arrange
|
||||
var reader = new ArReader(_inStream);
|
||||
|
||||
// Act
|
||||
using var ms = new MemoryStream();
|
||||
|
||||
reader.ReadFile("foo.bar", ms);
|
||||
ms.Seek(0, SeekOrigin.Begin);
|
||||
|
||||
// Assert
|
||||
Assert.AreEqual(0, ms.Length);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ShouldWriteNothingToDiskForMissingFile()
|
||||
{
|
||||
// Arrange
|
||||
string tmpFile = Path.GetTempFileName();
|
||||
var reader = new ArReader(_inStream);
|
||||
|
||||
try
|
||||
{
|
||||
// Act
|
||||
reader.ReadFile("foo.bar", tmpFile);
|
||||
|
||||
// Assert
|
||||
Assert.AreEqual(0, new FileInfo(tmpFile).Length);
|
||||
}
|
||||
finally
|
||||
{
|
||||
File.Delete(tmpFile);
|
||||
}
|
||||
}
|
||||
|
||||
private class OverrideStream : MemoryStream
|
||||
{
|
||||
public override bool CanWrite => CanWriteOR;
|
||||
|
||||
public bool CanWriteOR { get; set; }
|
||||
|
||||
public override bool CanSeek => CanSeekOR;
|
||||
|
||||
public bool CanSeekOR { get; set; }
|
||||
|
||||
public override bool CanRead => CanReadOR;
|
||||
|
||||
public bool CanReadOR { get; set; }
|
||||
}
|
||||
}
|
||||
}
|
||||
304
test/AMWD.Common.Tests/Packing/Ar/ArWriterTest.cs
Normal file
304
test/AMWD.Common.Tests/Packing/Ar/ArWriterTest.cs
Normal file
@@ -0,0 +1,304 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
using AMWD.Common.Packing.Ar;
|
||||
|
||||
namespace AMWD.Common.Tests.Packing.Ar
|
||||
{
|
||||
[TestClass]
|
||||
public class ArWriterTest
|
||||
{
|
||||
private readonly DateTime _fixedDateTime = new(2023, 03, 01, 10, 20, 30, 0, DateTimeKind.Utc);
|
||||
|
||||
private readonly Dictionary<string, string> _files = [];
|
||||
|
||||
private MemoryStream _outStream;
|
||||
|
||||
[TestInitialize]
|
||||
public void Initialize()
|
||||
{
|
||||
_files.Clear();
|
||||
for (int i = 0; i < 3; i++)
|
||||
{
|
||||
var (filePath, content) = GenerateTestFile();
|
||||
_files.Add(filePath, content);
|
||||
}
|
||||
|
||||
_outStream = new MemoryStream();
|
||||
}
|
||||
|
||||
[TestCleanup]
|
||||
public void Cleanup()
|
||||
{
|
||||
foreach (var kvp in _files)
|
||||
File.Delete(kvp.Key);
|
||||
|
||||
_files.Clear();
|
||||
|
||||
_outStream.Dispose();
|
||||
_outStream = null;
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ShouldInitializeArchive()
|
||||
{
|
||||
// Arrange
|
||||
byte[] initBytes = new byte[8];
|
||||
|
||||
// Act
|
||||
_ = new ArWriter(_outStream);
|
||||
|
||||
_outStream.Seek(0, SeekOrigin.Begin);
|
||||
|
||||
// Assert
|
||||
Assert.AreEqual(initBytes.Length, _outStream.Length);
|
||||
|
||||
_outStream.Read(initBytes, 0, initBytes.Length);
|
||||
CollectionAssert.AreEqual(Encoding.ASCII.GetBytes("!<arch>\n"), initBytes);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ShouldWriteOneFile()
|
||||
{
|
||||
// Arrange
|
||||
var firstFileKvp = _files.First();
|
||||
byte[] headerBytes = new byte[60];
|
||||
byte[] contentBytes = new byte[14];
|
||||
|
||||
var writer = new ArWriter(_outStream);
|
||||
|
||||
// Act
|
||||
writer.WriteFile(firstFileKvp.Key, 123, 456);
|
||||
|
||||
_outStream.Seek(8, SeekOrigin.Begin); // set behind init bytes
|
||||
|
||||
// Assert
|
||||
Assert.AreEqual(8 + headerBytes.Length + contentBytes.Length, _outStream.Length);
|
||||
|
||||
_outStream.Read(headerBytes, 0, headerBytes.Length);
|
||||
_outStream.Read(contentBytes, 0, contentBytes.Length);
|
||||
|
||||
string header = Encoding.ASCII.GetString(headerBytes);
|
||||
string content = Encoding.UTF8.GetString(contentBytes);
|
||||
|
||||
Assert.AreEqual($"{Path.GetFileName(firstFileKvp.Key),-16}1677666030 123 456 100644 14 `\n", header);
|
||||
Assert.AreEqual(firstFileKvp.Value, content);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ShouldWriteMultipleFiles()
|
||||
{
|
||||
// Arrange
|
||||
var writer = new ArWriter(_outStream);
|
||||
|
||||
// Act
|
||||
foreach (var kvp in _files)
|
||||
writer.WriteFile(kvp.Key, 123, 456);
|
||||
|
||||
_outStream.Seek(8, SeekOrigin.Begin);
|
||||
|
||||
// Assert
|
||||
Assert.AreEqual(8 + 3 * 60 + 3 * 14, _outStream.Length);
|
||||
|
||||
foreach (var kvp in _files)
|
||||
{
|
||||
byte[] headerBytes = new byte[60];
|
||||
byte[] contentBytes = new byte[14];
|
||||
|
||||
_outStream.Read(headerBytes, 0, headerBytes.Length);
|
||||
_outStream.Read(contentBytes, 0, contentBytes.Length);
|
||||
|
||||
string header = Encoding.ASCII.GetString(headerBytes);
|
||||
string content = Encoding.UTF8.GetString(contentBytes);
|
||||
|
||||
Assert.AreEqual($"{Path.GetFileName(kvp.Key),-16}1677666030 123 456 100644 14 `\n", header);
|
||||
Assert.AreEqual(kvp.Value, content);
|
||||
}
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ShouldPadToEven()
|
||||
{
|
||||
// Arrange
|
||||
var (filePath, fileContent) = GenerateTestFile(13);
|
||||
|
||||
try
|
||||
{
|
||||
byte[] headerBytes = new byte[60];
|
||||
byte[] contentBytes = new byte[14];
|
||||
|
||||
var writer = new ArWriter(_outStream);
|
||||
|
||||
// Act
|
||||
writer.WriteFile(filePath, 123, 456);
|
||||
|
||||
_outStream.Seek(8, SeekOrigin.Begin);
|
||||
|
||||
// Assert
|
||||
Assert.AreEqual(8 + headerBytes.Length + contentBytes.Length, _outStream.Length);
|
||||
|
||||
_outStream.Read(headerBytes, 0, headerBytes.Length);
|
||||
_outStream.Read(contentBytes, 0, contentBytes.Length);
|
||||
|
||||
string header = Encoding.ASCII.GetString(headerBytes);
|
||||
string content = Encoding.UTF8.GetString(contentBytes);
|
||||
|
||||
Assert.AreEqual($"{Path.GetFileName(filePath),-16}1677666030 123 456 100644 13 `\n", header);
|
||||
Assert.AreEqual(fileContent + "\n", content);
|
||||
}
|
||||
finally
|
||||
{
|
||||
File.Delete(filePath);
|
||||
}
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[ExpectedException(typeof(ArgumentException))]
|
||||
public void ShouldFailOnFileNameTooLong()
|
||||
{
|
||||
// Arrange
|
||||
var (filePath, _) = GenerateTestFile();
|
||||
try
|
||||
{
|
||||
string path = Path.GetDirectoryName(filePath);
|
||||
string fileName = Path.GetFileName(filePath);
|
||||
fileName = fileName.PadLeft(20, 'a');
|
||||
|
||||
File.Move(filePath, Path.Combine(path, fileName));
|
||||
filePath = Path.Combine(path, fileName);
|
||||
|
||||
var writer = new ArWriter(_outStream);
|
||||
|
||||
// Act
|
||||
writer.WriteFile(filePath, 123, 456);
|
||||
|
||||
// Assert - Exception
|
||||
Assert.Fail();
|
||||
}
|
||||
finally
|
||||
{
|
||||
File.Delete(filePath);
|
||||
}
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ShouldWriteEmptyOnNegativeUserId()
|
||||
{
|
||||
// Arrange
|
||||
var firstFileKvp = _files.First();
|
||||
byte[] headerBytes = new byte[60];
|
||||
byte[] contentBytes = new byte[14];
|
||||
|
||||
var writer = new ArWriter(_outStream);
|
||||
|
||||
// Act
|
||||
writer.WriteFile(firstFileKvp.Key, -123, 456);
|
||||
|
||||
_outStream.Seek(8, SeekOrigin.Begin);
|
||||
|
||||
// Assert
|
||||
Assert.AreEqual(8 + headerBytes.Length + contentBytes.Length, _outStream.Length);
|
||||
|
||||
_outStream.Read(headerBytes, 0, headerBytes.Length);
|
||||
_outStream.Read(contentBytes, 0, contentBytes.Length);
|
||||
|
||||
string header = Encoding.ASCII.GetString(headerBytes);
|
||||
string content = Encoding.UTF8.GetString(contentBytes);
|
||||
|
||||
Assert.AreEqual($"{Path.GetFileName(firstFileKvp.Key),-16}1677666030 456 100644 14 `\n", header);
|
||||
Assert.AreEqual(firstFileKvp.Value, content);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ShouldWriteEmptyOnNegativeGroupId()
|
||||
{
|
||||
// Arrange
|
||||
var firstFileKvp = _files.First();
|
||||
byte[] headerBytes = new byte[60];
|
||||
byte[] contentBytes = new byte[14];
|
||||
|
||||
var writer = new ArWriter(_outStream);
|
||||
|
||||
// Act
|
||||
writer.WriteFile(firstFileKvp.Key, 123, -456);
|
||||
|
||||
_outStream.Seek(8, SeekOrigin.Begin);
|
||||
|
||||
// Assert
|
||||
Assert.AreEqual(8 + headerBytes.Length + contentBytes.Length, _outStream.Length);
|
||||
|
||||
_outStream.Read(headerBytes, 0, headerBytes.Length);
|
||||
_outStream.Read(contentBytes, 0, contentBytes.Length);
|
||||
|
||||
string header = Encoding.ASCII.GetString(headerBytes);
|
||||
string content = Encoding.UTF8.GetString(contentBytes);
|
||||
|
||||
Assert.AreEqual($"{Path.GetFileName(firstFileKvp.Key),-16}1677666030 123 100644 14 `\n", header);
|
||||
Assert.AreEqual(firstFileKvp.Value, content);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ShouldWriteEmptyOnNegativeMode()
|
||||
{
|
||||
// Arrange
|
||||
var firstFileKvp = _files.First();
|
||||
byte[] headerBytes = new byte[60];
|
||||
byte[] contentBytes = new byte[14];
|
||||
|
||||
var writer = new ArWriter(_outStream);
|
||||
|
||||
// Act
|
||||
writer.WriteFile(firstFileKvp.Key, 123, 456, -1);
|
||||
|
||||
_outStream.Seek(8, SeekOrigin.Begin);
|
||||
|
||||
// Assert
|
||||
Assert.AreEqual(8 + headerBytes.Length + contentBytes.Length, _outStream.Length);
|
||||
|
||||
_outStream.Read(headerBytes, 0, headerBytes.Length);
|
||||
_outStream.Read(contentBytes, 0, contentBytes.Length);
|
||||
|
||||
string header = Encoding.ASCII.GetString(headerBytes);
|
||||
string content = Encoding.UTF8.GetString(contentBytes);
|
||||
|
||||
Assert.AreEqual($"{Path.GetFileName(firstFileKvp.Key),-16}1677666030 123 456 14 `\n", header);
|
||||
Assert.AreEqual(firstFileKvp.Value, content);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[ExpectedException(typeof(ArgumentException))]
|
||||
public void ShouldFailOnNonWritableStream()
|
||||
{
|
||||
// Arrange
|
||||
using var testStream = new NonWriteStream();
|
||||
|
||||
// Act
|
||||
_ = new ArWriter(testStream);
|
||||
|
||||
// Assert - ArgumentException
|
||||
Assert.Fail();
|
||||
}
|
||||
|
||||
private (string filePath, string content) GenerateTestFile(int length = 14)
|
||||
{
|
||||
string filePath = Path.GetTempFileName();
|
||||
string text = CryptographyHelper.GetRandomString(length);
|
||||
|
||||
File.WriteAllText(filePath, text);
|
||||
File.SetLastWriteTimeUtc(filePath, _fixedDateTime);
|
||||
return (filePath, text);
|
||||
}
|
||||
|
||||
private class NonWriteStream : MemoryStream
|
||||
{
|
||||
public NonWriteStream()
|
||||
: base()
|
||||
{ }
|
||||
|
||||
public override bool CanWrite => false;
|
||||
}
|
||||
}
|
||||
}
|
||||
350
test/AMWD.Common.Tests/Utilities/AsyncQueueTest.cs
Normal file
350
test/AMWD.Common.Tests/Utilities/AsyncQueueTest.cs
Normal file
@@ -0,0 +1,350 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Reflection;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace AMWD.Common.Tests.Utilities
|
||||
{
|
||||
[TestClass]
|
||||
public class AsyncQueueTest
|
||||
{
|
||||
private Queue<TestElement> _internalQueue;
|
||||
|
||||
private TestElement _queueElement1;
|
||||
private TestElement _queueElement2;
|
||||
private TestElement _queueElement3;
|
||||
|
||||
[TestInitialize]
|
||||
public void InitializeTest()
|
||||
{
|
||||
_queueElement1 = new TestElement
|
||||
{
|
||||
Number = 111,
|
||||
Text = "one"
|
||||
};
|
||||
_queueElement2 = new TestElement
|
||||
{
|
||||
Number = 222,
|
||||
Text = "two"
|
||||
};
|
||||
_queueElement3 = new TestElement
|
||||
{
|
||||
Number = 333,
|
||||
Text = "three"
|
||||
};
|
||||
|
||||
_internalQueue = new Queue<TestElement>();
|
||||
_internalQueue.Enqueue(_queueElement1);
|
||||
_internalQueue.Enqueue(_queueElement2);
|
||||
_internalQueue.Enqueue(_queueElement3);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ShouldEnqueueItem()
|
||||
{
|
||||
// arrange
|
||||
var element = new TestElement { Number = 1, Text = "Hello" };
|
||||
|
||||
_internalQueue.Clear();
|
||||
var queue = GetQueue();
|
||||
|
||||
// act
|
||||
queue.Enqueue(element);
|
||||
|
||||
// assert
|
||||
Assert.AreEqual(1, _internalQueue.Count);
|
||||
Assert.AreEqual(_internalQueue.Count, queue.Count);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ShouldEnqueueItemAndResetAvailableToken()
|
||||
{
|
||||
// arrange
|
||||
var element = new TestElement { Number = 1, Text = "Hello" };
|
||||
bool available = false;
|
||||
|
||||
_internalQueue.Clear();
|
||||
var queue = GetQueue();
|
||||
|
||||
// act
|
||||
var task = Task.Run(async () =>
|
||||
{
|
||||
await queue.WaitAsync();
|
||||
available = true;
|
||||
});
|
||||
queue.Enqueue(element);
|
||||
task.Wait();
|
||||
|
||||
// assert
|
||||
Assert.IsTrue(available);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ShouldEnqueueItemAndResetDequeueToken()
|
||||
{
|
||||
// arrange
|
||||
var element = new TestElement { Number = 1, Text = "Hello" };
|
||||
TestElement callback = null;
|
||||
|
||||
_internalQueue.Clear();
|
||||
var queue = GetQueue();
|
||||
|
||||
// act
|
||||
var task = Task.Run(async () =>
|
||||
{
|
||||
callback = await queue.DequeueAsync();
|
||||
});
|
||||
queue.Enqueue(element);
|
||||
task.Wait();
|
||||
|
||||
// assert
|
||||
Assert.IsNotNull(callback);
|
||||
Assert.AreEqual(element, callback);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ShouldEnqueueMultipleItems()
|
||||
{
|
||||
// arrange
|
||||
var elements = new TestElement[]
|
||||
{
|
||||
new() { Number = 1, Text = "Hello" },
|
||||
new() { Number = 2, Text = "World" },
|
||||
};
|
||||
_internalQueue.Clear();
|
||||
var queue = GetQueue();
|
||||
|
||||
// act
|
||||
queue.Enqueue(elements);
|
||||
|
||||
// assert
|
||||
Assert.AreEqual(2, _internalQueue.Count);
|
||||
Assert.AreEqual(queue.Count, _internalQueue.Count);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ShouldPeekAValue()
|
||||
{
|
||||
// arrange
|
||||
var queue = GetQueue();
|
||||
|
||||
// act
|
||||
bool isSuccess = queue.TryPeek(out var item);
|
||||
|
||||
// assert
|
||||
Assert.IsTrue(isSuccess);
|
||||
Assert.IsNotNull(item);
|
||||
Assert.AreEqual(_queueElement1, item);
|
||||
Assert.AreEqual(3, queue.Count);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ShouldNotPeekAValue()
|
||||
{
|
||||
// arrange
|
||||
_internalQueue.Clear();
|
||||
var queue = GetQueue();
|
||||
|
||||
// act
|
||||
bool isSuccess = queue.TryPeek(out var item);
|
||||
|
||||
// assert
|
||||
Assert.IsFalse(isSuccess);
|
||||
Assert.IsNull(item);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ShouldDequeueAValue()
|
||||
{
|
||||
// arrange
|
||||
var queue = GetQueue();
|
||||
|
||||
// act
|
||||
bool isSuccess = queue.TryDequeue(out var item);
|
||||
|
||||
// assert
|
||||
Assert.IsTrue(isSuccess);
|
||||
Assert.IsNotNull(item);
|
||||
Assert.AreEqual(_queueElement1, item);
|
||||
Assert.AreEqual(2, queue.Count);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ShouldNotDequeueAValue()
|
||||
{
|
||||
// arrange
|
||||
_internalQueue.Clear();
|
||||
var queue = GetQueue();
|
||||
|
||||
// act
|
||||
bool isSuccess = queue.TryDequeue(out var item);
|
||||
|
||||
// assert
|
||||
Assert.IsFalse(isSuccess);
|
||||
Assert.IsNull(item);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ShouldRemoveAValue()
|
||||
{
|
||||
// arrange
|
||||
var queue = GetQueue();
|
||||
|
||||
// act
|
||||
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);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ShouldNotRemoveAValue()
|
||||
{
|
||||
// arrange
|
||||
var queue = GetQueue();
|
||||
|
||||
// act
|
||||
queue.Remove(null);
|
||||
var item1 = queue.Dequeue();
|
||||
var item2 = queue.Dequeue();
|
||||
var item3 = queue.Dequeue();
|
||||
|
||||
// assert
|
||||
Assert.AreEqual(0, queue.Count);
|
||||
Assert.AreEqual(_queueElement1, item1);
|
||||
Assert.AreEqual(_queueElement2, item2);
|
||||
Assert.AreEqual(_queueElement3, item3);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public async Task ShouldAwaitOneDequeue()
|
||||
{
|
||||
// arrange
|
||||
_internalQueue.Clear();
|
||||
var queue = GetQueue();
|
||||
|
||||
var task = Task.Run(async () =>
|
||||
{
|
||||
await Task.Delay(1000);
|
||||
queue.Enqueue(new[] { _queueElement1, _queueElement2, _queueElement3 });
|
||||
});
|
||||
|
||||
// act
|
||||
var item = await queue.DequeueAsync();
|
||||
|
||||
// assert
|
||||
Assert.AreEqual(2, queue.Count);
|
||||
Assert.IsNotNull(item);
|
||||
Assert.AreEqual(_queueElement1, item);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public async Task ShouldAwaitManyDequeue()
|
||||
{
|
||||
// arrange
|
||||
_internalQueue.Clear();
|
||||
var queue = GetQueue();
|
||||
|
||||
var task = Task.Run(async () =>
|
||||
{
|
||||
await Task.Delay(1000);
|
||||
queue.Enqueue(new[] { _queueElement1, _queueElement2, _queueElement3 });
|
||||
});
|
||||
|
||||
// act
|
||||
var items = await queue.DequeueManyAsync(2);
|
||||
|
||||
// assert
|
||||
Assert.AreEqual(1, queue.Count);
|
||||
Assert.IsNotNull(items);
|
||||
Assert.AreEqual(2, items.Length);
|
||||
Assert.AreEqual(_queueElement1, items[0]);
|
||||
Assert.AreEqual(_queueElement2, items[1]);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public async Task ShouldAwaitAllDequeue()
|
||||
{
|
||||
// arrange
|
||||
_internalQueue.Clear();
|
||||
var queue = GetQueue();
|
||||
|
||||
var task = Task.Run(async () =>
|
||||
{
|
||||
await Task.Delay(1000);
|
||||
queue.Enqueue(new[] { _queueElement1, _queueElement2, _queueElement3 });
|
||||
});
|
||||
|
||||
// act
|
||||
var items = await queue.DequeueAvailableAsync();
|
||||
|
||||
// assert
|
||||
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]);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public async Task ShouldAwaitAvailableDequeue()
|
||||
{
|
||||
// arrange
|
||||
_internalQueue.Clear();
|
||||
var queue = GetQueue();
|
||||
|
||||
var task = Task.Run(async () =>
|
||||
{
|
||||
await Task.Delay(1000);
|
||||
queue.Enqueue(new[] { _queueElement1, _queueElement2, _queueElement3 });
|
||||
});
|
||||
|
||||
// act
|
||||
var items = await queue.DequeueAvailableAsync(2);
|
||||
|
||||
// assert
|
||||
Assert.AreEqual(1, queue.Count);
|
||||
Assert.IsNotNull(items);
|
||||
Assert.AreEqual(2, items.Length);
|
||||
Assert.AreEqual(_queueElement1, items[0]);
|
||||
Assert.AreEqual(_queueElement2, items[1]);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[ExpectedException(typeof(ArgumentOutOfRangeException))]
|
||||
public async Task ShouldThrowArumentOutOfRangeException()
|
||||
{
|
||||
// arrange
|
||||
_internalQueue.Clear();
|
||||
var queue = GetQueue();
|
||||
|
||||
// act
|
||||
await queue.DequeueManyAsync(-2);
|
||||
|
||||
// assert - ArgumentOutOfRangeException expected
|
||||
Assert.Fail();
|
||||
}
|
||||
|
||||
private AsyncQueue<TestElement> GetQueue()
|
||||
{
|
||||
var asyncQueue = new AsyncQueue<TestElement>();
|
||||
|
||||
var field = asyncQueue.GetType().GetField("_queue", BindingFlags.Instance | BindingFlags.NonPublic);
|
||||
field.SetValue(asyncQueue, _internalQueue);
|
||||
|
||||
return asyncQueue;
|
||||
}
|
||||
|
||||
private class TestElement
|
||||
{
|
||||
public int Number { get; set; }
|
||||
|
||||
public string Text { get; set; }
|
||||
}
|
||||
}
|
||||
}
|
||||
635
test/AMWD.Common.Tests/Utilities/CryptographyHelperTest.cs
Normal file
635
test/AMWD.Common.Tests/Utilities/CryptographyHelperTest.cs
Normal file
@@ -0,0 +1,635 @@
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Security.Cryptography;
|
||||
using System.Text.RegularExpressions;
|
||||
using AMWD.Common.Tests.Utils;
|
||||
|
||||
namespace UnitTests.Common.Utilities
|
||||
{
|
||||
[TestClass]
|
||||
public partial class CryptographyHelperTests
|
||||
{
|
||||
private string _keyFile;
|
||||
private CryptographyHelper _cryptoHelper;
|
||||
|
||||
[TestInitialize]
|
||||
public void Initialize()
|
||||
{
|
||||
_keyFile = Path.GetTempFileName();
|
||||
_cryptoHelper = new CryptographyHelper(_keyFile);
|
||||
}
|
||||
|
||||
[TestCleanup]
|
||||
public void Cleanup()
|
||||
{
|
||||
File.Delete(_keyFile);
|
||||
}
|
||||
|
||||
#region Static
|
||||
|
||||
#region Encryption
|
||||
|
||||
#region AES
|
||||
|
||||
[TestMethod]
|
||||
public void ShouldEncryptAesWithoutSalt() // required to test the encryption itself
|
||||
{
|
||||
// arrange
|
||||
using var _ = CryptographyHelperSaltMock.Create(0);
|
||||
|
||||
byte[] bytes = [0xaf, 0xfe];
|
||||
string str = "ABC";
|
||||
string password1 = "P@ssw0rd!";
|
||||
string password2 = "P@ssw0rd";
|
||||
|
||||
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);
|
||||
string cipherStr1 = CryptographyHelper.AesEncrypt(str, password1);
|
||||
byte[] cipherBytes2 = CryptographyHelper.AesEncrypt(bytes, password2);
|
||||
string cipherStr2 = CryptographyHelper.AesEncrypt(str, password2);
|
||||
|
||||
// assert
|
||||
CollectionAssert.AreEqual(expectedBytes, cipherBytes1);
|
||||
Assert.AreEqual("ueLuhFNpCuYmx8v3hczHtg==", cipherStr1);
|
||||
|
||||
CollectionAssert.AreNotEqual(expectedBytes, cipherBytes2);
|
||||
Assert.AreNotEqual("ueLuhFNpCuYmx8v3hczHtg==", cipherStr2);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ShouldDecryptAesWithoutSalt() // required to test the decryption itself
|
||||
{
|
||||
// arrange
|
||||
using var _ = CryptographyHelperSaltMock.Create(0);
|
||||
|
||||
string cipherStr = "ueLuhFNpCuYmx8v3hczHtg==";
|
||||
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 = [0xaf, 0xfe];
|
||||
|
||||
// act
|
||||
byte[] plainBytes1 = CryptographyHelper.AesDecrypt(cipherBytes, password1);
|
||||
string plainStr1 = CryptographyHelper.AesDecrypt(cipherStr, password1);
|
||||
|
||||
try
|
||||
{
|
||||
CryptographyHelper.AesDecrypt(cipherBytes, password2);
|
||||
Assert.Fail();
|
||||
}
|
||||
catch (CryptographicException)
|
||||
{ }
|
||||
|
||||
try
|
||||
{
|
||||
CryptographyHelper.AesDecrypt(cipherStr, password2);
|
||||
Assert.Fail();
|
||||
}
|
||||
catch (CryptographicException)
|
||||
{ }
|
||||
|
||||
// assert
|
||||
CollectionAssert.AreEqual(expectedBytes, plainBytes1);
|
||||
Assert.AreEqual("ABC", plainStr1);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ShouldEncryptDecryptAesBytes()
|
||||
{
|
||||
// arrange
|
||||
byte[] plain = [0xaf, 0xfe];
|
||||
string password = "P@ssw0rd!";
|
||||
|
||||
// act
|
||||
byte[] cipher1 = CryptographyHelper.AesEncrypt(plain, password);
|
||||
byte[] cipher2 = CryptographyHelper.AesEncrypt(plain, password);
|
||||
|
||||
byte[] plain1 = CryptographyHelper.AesDecrypt(cipher1, password);
|
||||
byte[] plain2 = CryptographyHelper.AesDecrypt(cipher2, password);
|
||||
|
||||
// assert
|
||||
CollectionAssert.AreNotEqual(cipher1, cipher2);
|
||||
Assert.AreEqual(24, cipher1.Length);
|
||||
Assert.AreEqual(24, cipher2.Length);
|
||||
CollectionAssert.AreEqual(plain, plain1);
|
||||
CollectionAssert.AreEqual(plain, plain2);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ShouldEncryptDecryptAesString()
|
||||
{
|
||||
// arrange
|
||||
string plain = "ABC";
|
||||
string password = "P@ssw0rd!";
|
||||
|
||||
// act
|
||||
string cipher1 = CryptographyHelper.AesEncrypt(plain, password);
|
||||
string cipher2 = CryptographyHelper.AesEncrypt(plain, password);
|
||||
|
||||
string plain1 = CryptographyHelper.AesDecrypt(cipher1, password);
|
||||
string plain2 = CryptographyHelper.AesDecrypt(cipher2, password);
|
||||
|
||||
// assert
|
||||
Assert.AreNotEqual(cipher1, cipher2);
|
||||
Assert.AreEqual(32, cipher1.Length);
|
||||
Assert.AreEqual(32, cipher2.Length);
|
||||
Assert.AreEqual(plain, plain1);
|
||||
Assert.AreEqual(plain, plain2);
|
||||
}
|
||||
|
||||
#endregion AES
|
||||
|
||||
#region TripleDES
|
||||
|
||||
[TestMethod]
|
||||
public void ShouldEncryptTdesWithoutSalt() // required to test the encryption itself
|
||||
{
|
||||
// arrange
|
||||
using var _ = CryptographyHelperSaltMock.Create(0);
|
||||
|
||||
byte[] bytes = [0xaf, 0xfe];
|
||||
string str = "ABC";
|
||||
string password1 = "P@ssw0rd!";
|
||||
string password2 = "P@ssw0rd";
|
||||
|
||||
byte[] expectedBytes = [0xbf, 0x59, 0x1f, 0x48, 0x69, 0xab, 0x18, 0xc7];
|
||||
|
||||
// act
|
||||
byte[] cipherBytes1 = CryptographyHelper.TripleDesEncrypt(bytes, password1);
|
||||
string cipherStr1 = CryptographyHelper.TripleDesEncrypt(str, password1);
|
||||
byte[] cipherBytes2 = CryptographyHelper.TripleDesEncrypt(bytes, password2);
|
||||
string cipherStr2 = CryptographyHelper.TripleDesEncrypt(str, password2);
|
||||
|
||||
// assert
|
||||
CollectionAssert.AreEqual(expectedBytes, cipherBytes1);
|
||||
Assert.AreEqual("1l74soBuuEI=", cipherStr1);
|
||||
|
||||
CollectionAssert.AreNotEqual(expectedBytes, cipherBytes2);
|
||||
Assert.AreNotEqual("1l74soBuuEI=", cipherStr2);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ShouldDecryptTdesWithoutSalt() // required to test the decryption itself
|
||||
{
|
||||
// arrange
|
||||
using var _ = CryptographyHelperSaltMock.Create(0);
|
||||
|
||||
string cipherStr = "1l74soBuuEI=";
|
||||
byte[] cipherBytes = [0xbf, 0x59, 0x1f, 0x48, 0x69, 0xab, 0x18, 0xc7];
|
||||
|
||||
string password1 = "P@ssw0rd!";
|
||||
string password2 = "P@ssw0rd";
|
||||
|
||||
byte[] expectedBytes = [0xaf, 0xfe];
|
||||
|
||||
// act
|
||||
byte[] plainBytes1 = CryptographyHelper.TripleDesDecrypt(cipherBytes, password1);
|
||||
string plainStr1 = CryptographyHelper.TripleDesDecrypt(cipherStr, password1);
|
||||
|
||||
try
|
||||
{
|
||||
CryptographyHelper.TripleDesDecrypt(cipherBytes, password2);
|
||||
Assert.Fail();
|
||||
}
|
||||
catch (CryptographicException)
|
||||
{ }
|
||||
|
||||
try
|
||||
{
|
||||
CryptographyHelper.TripleDesDecrypt(cipherStr, password2);
|
||||
Assert.Fail();
|
||||
}
|
||||
catch (CryptographicException)
|
||||
{ }
|
||||
|
||||
// assert
|
||||
CollectionAssert.AreEqual(expectedBytes, plainBytes1);
|
||||
Assert.AreEqual("ABC", plainStr1);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ShouldEncryptDecryptTdesBytes()
|
||||
{
|
||||
// arrange
|
||||
byte[] plain = [0xaf, 0xfe];
|
||||
string password = "P@ssw0rd!";
|
||||
|
||||
// act
|
||||
byte[] cipher1 = CryptographyHelper.TripleDesEncrypt(plain, password);
|
||||
byte[] cipher2 = CryptographyHelper.TripleDesEncrypt(plain, password);
|
||||
|
||||
byte[] plain1 = CryptographyHelper.TripleDesDecrypt(cipher1, password);
|
||||
byte[] plain2 = CryptographyHelper.TripleDesDecrypt(cipher2, password);
|
||||
|
||||
// assert
|
||||
CollectionAssert.AreNotEqual(cipher1, cipher2);
|
||||
Assert.AreEqual(16, cipher1.Length);
|
||||
Assert.AreEqual(16, cipher2.Length);
|
||||
CollectionAssert.AreEqual(plain, plain1);
|
||||
CollectionAssert.AreEqual(plain, plain2);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ShouldEncryptDecryptTdesString()
|
||||
{
|
||||
// arrange
|
||||
string plain = "ABC";
|
||||
string password = "P@ssw0rd!";
|
||||
|
||||
// act
|
||||
string cipher1 = CryptographyHelper.TripleDesEncrypt(plain, password);
|
||||
string cipher2 = CryptographyHelper.TripleDesEncrypt(plain, password);
|
||||
|
||||
string plain1 = CryptographyHelper.TripleDesDecrypt(cipher1, password);
|
||||
string plain2 = CryptographyHelper.TripleDesDecrypt(cipher2, password);
|
||||
|
||||
// assert
|
||||
Assert.AreNotEqual(cipher1, cipher2);
|
||||
Assert.AreEqual(24, cipher1.Length);
|
||||
Assert.AreEqual(24, cipher2.Length);
|
||||
Assert.AreEqual(plain, plain1);
|
||||
Assert.AreEqual(plain, plain2);
|
||||
}
|
||||
|
||||
#endregion TripleDES
|
||||
|
||||
#endregion Encryption
|
||||
|
||||
#region Hash
|
||||
|
||||
[TestMethod]
|
||||
public void ShouldReturnMd5Hash()
|
||||
{
|
||||
// arrange
|
||||
string text = "Hello World!";
|
||||
byte[] bytes = [0xde, 0xad, 0xbe, 0xef];
|
||||
string fileName = Path.GetTempFileName();
|
||||
|
||||
// act
|
||||
string textHash = CryptographyHelper.Md5(text);
|
||||
string bytesHash = CryptographyHelper.Md5(bytes);
|
||||
|
||||
File.WriteAllText(fileName, text);
|
||||
string fileHash = CryptographyHelper.Md5File(fileName);
|
||||
File.Delete(fileName);
|
||||
|
||||
// assert
|
||||
Assert.AreEqual("ed076287532e86365e841e92bfc50d8c", textHash);
|
||||
Assert.AreEqual("2f249230a8e7c2bf6005ccd2679259ec", bytesHash);
|
||||
Assert.AreEqual("ed076287532e86365e841e92bfc50d8c", fileHash);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ShouldReturnSha1Hash()
|
||||
{
|
||||
// arrange
|
||||
string text = "Hello World!";
|
||||
byte[] bytes = [0xde, 0xad, 0xbe, 0xef];
|
||||
string fileName = Path.GetTempFileName();
|
||||
|
||||
// act
|
||||
string textHash = CryptographyHelper.Sha1(text);
|
||||
string bytesHash = CryptographyHelper.Sha1(bytes);
|
||||
|
||||
File.WriteAllText(fileName, text);
|
||||
string fileHash = CryptographyHelper.Sha1File(fileName);
|
||||
File.Delete(fileName);
|
||||
|
||||
// assert
|
||||
Assert.AreEqual("2ef7bde608ce5404e97d5f042f95f89f1c232871", textHash);
|
||||
Assert.AreEqual("d78f8bb992a56a597f6c7a1fb918bb78271367eb", bytesHash);
|
||||
Assert.AreEqual("2ef7bde608ce5404e97d5f042f95f89f1c232871", fileHash);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ShouldReturnSha256Hash()
|
||||
{
|
||||
// arrange
|
||||
string text = "Hello World!";
|
||||
byte[] bytes = [0xde, 0xad, 0xbe, 0xef];
|
||||
string fileName = Path.GetTempFileName();
|
||||
|
||||
// act
|
||||
string textHash = CryptographyHelper.Sha256(text);
|
||||
string bytesHash = CryptographyHelper.Sha256(bytes);
|
||||
|
||||
File.WriteAllText(fileName, text);
|
||||
string fileHash = CryptographyHelper.Sha256File(fileName);
|
||||
File.Delete(fileName);
|
||||
|
||||
// assert
|
||||
Assert.AreEqual("7f83b1657ff1fc53b92dc18148a1d65dfc2d4b1fa3d677284addd200126d9069", textHash);
|
||||
Assert.AreEqual("5f78c33274e43fa9de5659265c1d917e25c03722dcb0b8d27db8d5feaa813953", bytesHash);
|
||||
Assert.AreEqual("7f83b1657ff1fc53b92dc18148a1d65dfc2d4b1fa3d677284addd200126d9069", fileHash);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ShouldReturnSha512Hash()
|
||||
{
|
||||
// arrange
|
||||
string text = "Hello World!";
|
||||
byte[] bytes = [0xde, 0xad, 0xbe, 0xef];
|
||||
string fileName = Path.GetTempFileName();
|
||||
|
||||
// act
|
||||
string textHash = CryptographyHelper.Sha512(text);
|
||||
string bytesHash = CryptographyHelper.Sha512(bytes);
|
||||
|
||||
File.WriteAllText(fileName, text);
|
||||
string fileHash = CryptographyHelper.Sha512File(fileName);
|
||||
File.Delete(fileName);
|
||||
|
||||
// assert
|
||||
Assert.AreEqual("861844d6704e8573fec34d967e20bcfef3d424cf48be04e6dc08f2bd58c729743371015ead891cc3cf1c9d34b49264b510751b1ff9e537937bc46b5d6ff4ecc8", textHash);
|
||||
Assert.AreEqual("1284b2d521535196f22175d5f558104220a6ad7680e78b49fa6f20e57ea7b185d71ec1edb137e70eba528dedb141f5d2f8bb53149d262932b27cf41fed96aa7f", bytesHash);
|
||||
Assert.AreEqual("861844d6704e8573fec34d967e20bcfef3d424cf48be04e6dc08f2bd58c729743371015ead891cc3cf1c9d34b49264b510751b1ff9e537937bc46b5d6ff4ecc8", fileHash);
|
||||
}
|
||||
|
||||
#endregion Hash
|
||||
|
||||
#region Random
|
||||
|
||||
[TestMethod]
|
||||
public void ShouldReturnRandomBytes()
|
||||
{
|
||||
// arrange
|
||||
int length1 = 12;
|
||||
int length2 = 12;
|
||||
int length3 = 42;
|
||||
|
||||
// act
|
||||
byte[] bytes1 = CryptographyHelper.GetRandomBytes(length1);
|
||||
byte[] bytes2 = CryptographyHelper.GetRandomBytes(length2);
|
||||
byte[] bytes3 = CryptographyHelper.GetRandomBytes(length3);
|
||||
|
||||
// assert
|
||||
Assert.AreEqual(length1, bytes1.Length);
|
||||
Assert.AreEqual(length2, bytes2.Length);
|
||||
Assert.AreEqual(length3, bytes3.Length);
|
||||
|
||||
Assert.IsTrue(bytes1.Length == bytes2.Length);
|
||||
CollectionAssert.AreNotEqual(bytes1, bytes2);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ShouldReturnRandomString()
|
||||
{
|
||||
// arrange
|
||||
int length1 = 12;
|
||||
int length2 = 12;
|
||||
int length3 = 42;
|
||||
|
||||
// act
|
||||
string str1 = CryptographyHelper.GetRandomString(length1);
|
||||
string str2 = CryptographyHelper.GetRandomString(length2);
|
||||
string str3 = CryptographyHelper.GetRandomString(length3);
|
||||
|
||||
// assert
|
||||
Assert.AreEqual(length1, str1.Length);
|
||||
Assert.AreEqual(length2, str2.Length);
|
||||
Assert.AreEqual(length3, str3.Length);
|
||||
|
||||
Assert.IsTrue(str1.Length == str2.Length);
|
||||
Assert.IsFalse(str1 == str2);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ShouldReturnRandomStringWithPool()
|
||||
{
|
||||
// arrange
|
||||
int length = 12;
|
||||
string pool = "0123456789abcdef";
|
||||
|
||||
// act
|
||||
string str1 = CryptographyHelper.GetRandomString(length, pool);
|
||||
string str2 = CryptographyHelper.GetRandomString(length, pool);
|
||||
|
||||
// assert
|
||||
Assert.AreEqual(length, str1.Length);
|
||||
Assert.AreEqual(length, str2.Length);
|
||||
Assert.IsFalse(str1 == str2);
|
||||
Assert.IsFalse(RandomStringWithPoolRegex().IsMatch(str1));
|
||||
Assert.IsFalse(RandomStringWithPoolRegex().IsMatch(str2));
|
||||
}
|
||||
|
||||
#endregion Random
|
||||
|
||||
#region Secure probing
|
||||
|
||||
[TestMethod]
|
||||
public void ShouldTakeSameTimeToCompareString()
|
||||
{
|
||||
// arrange
|
||||
string str1 = "Hello World!";
|
||||
string str2 = "Hello World!";
|
||||
string str3 = "Hallo World!";
|
||||
string str4 = "Hello World?";
|
||||
string nullStr = null;
|
||||
string strLen = "Hello World";
|
||||
|
||||
var sw = new Stopwatch();
|
||||
|
||||
// act
|
||||
bool nullCompare = CryptographyHelper.SecureEquals(nullStr, str1);
|
||||
bool lenCompare = CryptographyHelper.SecureEquals(strLen, str1);
|
||||
|
||||
sw.Start();
|
||||
bool compare1 = CryptographyHelper.SecureEquals(str1, str2);
|
||||
long time1 = sw.ElapsedMilliseconds;
|
||||
|
||||
bool compare2 = CryptographyHelper.SecureEquals(str1, str3);
|
||||
long time2 = sw.ElapsedMilliseconds;
|
||||
|
||||
bool compare3 = CryptographyHelper.SecureEquals(str1, str4);
|
||||
long time3 = sw.ElapsedMilliseconds;
|
||||
sw.Stop();
|
||||
|
||||
// assert
|
||||
Assert.IsFalse(nullCompare);
|
||||
Assert.IsFalse(lenCompare);
|
||||
Assert.IsTrue(compare1);
|
||||
Assert.IsFalse(compare2);
|
||||
Assert.IsFalse(compare3);
|
||||
|
||||
Assert.AreEqual(time1, time2);
|
||||
Assert.AreEqual(time1, time3);
|
||||
Assert.AreEqual(time2, time3);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ShouldTakeSameTimeToCompareBytes()
|
||||
{
|
||||
// arrange
|
||||
byte[] bytes1 = CryptographyHelper.GetRandomBytes(200);
|
||||
byte[] bytes2 = new byte[bytes1.Length];
|
||||
byte[] bytes3 = new byte[bytes1.Length];
|
||||
byte[] bytes4 = new byte[bytes1.Length];
|
||||
byte[] nullBytes = null;
|
||||
byte[] lenBytes = new byte[bytes1.Length + 1];
|
||||
|
||||
Array.Copy(bytes1, bytes2, bytes1.Length);
|
||||
Array.Copy(bytes1, bytes3, bytes1.Length);
|
||||
Array.Copy(bytes1, bytes4, bytes1.Length);
|
||||
|
||||
bytes3[10] = (byte)(bytes1[10] + 1);
|
||||
bytes4[190] = (byte)(bytes1[190] + 1);
|
||||
|
||||
var sw = new Stopwatch();
|
||||
|
||||
// act
|
||||
bool nullCompare = CryptographyHelper.SecureEquals(nullBytes, bytes1);
|
||||
bool lenCompare = CryptographyHelper.SecureEquals(lenBytes, bytes1);
|
||||
|
||||
sw.Start();
|
||||
bool compare1 = CryptographyHelper.SecureEquals(bytes1, bytes2);
|
||||
long time1 = sw.ElapsedMilliseconds;
|
||||
|
||||
bool compare2 = CryptographyHelper.SecureEquals(bytes1, bytes3);
|
||||
long time2 = sw.ElapsedMilliseconds;
|
||||
|
||||
bool compare3 = CryptographyHelper.SecureEquals(bytes1, bytes4);
|
||||
long time3 = sw.ElapsedMilliseconds;
|
||||
sw.Stop();
|
||||
|
||||
// assert
|
||||
Assert.IsFalse(nullCompare);
|
||||
Assert.IsFalse(lenCompare);
|
||||
Assert.IsTrue(compare1);
|
||||
Assert.IsFalse(compare2);
|
||||
Assert.IsFalse(compare3);
|
||||
|
||||
Assert.AreEqual(time1, time2);
|
||||
Assert.AreEqual(time1, time3);
|
||||
Assert.AreEqual(time2, time3);
|
||||
}
|
||||
|
||||
#endregion Secure probing
|
||||
|
||||
#endregion Static
|
||||
|
||||
#region Instance
|
||||
|
||||
[TestMethod]
|
||||
public void ShouldCreateDefaultFile()
|
||||
{
|
||||
// arrange
|
||||
string executingAssemblyDir = AppContext.BaseDirectory;
|
||||
string filePath = Path.Combine(executingAssemblyDir, "crypto.key");
|
||||
|
||||
// act
|
||||
bool fileExistsBefore = File.Exists(filePath);
|
||||
var helper = new CryptographyHelper();
|
||||
bool fileExistsAfter = File.Exists(filePath);
|
||||
string content = fileExistsAfter ? File.ReadAllText(filePath) : null;
|
||||
File.Delete(filePath);
|
||||
|
||||
// assert
|
||||
Assert.IsFalse(fileExistsBefore);
|
||||
Assert.IsNotNull(helper);
|
||||
Assert.IsTrue(fileExistsAfter);
|
||||
Assert.IsTrue(!string.IsNullOrWhiteSpace(content));
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ShouldEncryptAesUsingKeyFile()
|
||||
{
|
||||
// arrange
|
||||
string str = "Hello World!";
|
||||
byte[] bytes = CryptographyHelper.GetRandomBytes(32);
|
||||
|
||||
string password = File.ReadAllText(_keyFile);
|
||||
|
||||
// act
|
||||
string cipherStr = _cryptoHelper.EncryptAes(str);
|
||||
byte[] cipherBytes = _cryptoHelper.EncryptAes(bytes);
|
||||
|
||||
string plainStr = CryptographyHelper.AesDecrypt(cipherStr, password);
|
||||
byte[] plainBytes = CryptographyHelper.AesDecrypt(cipherBytes, password);
|
||||
|
||||
// assert
|
||||
Assert.AreNotEqual(str, cipherStr);
|
||||
Assert.AreEqual(str, plainStr);
|
||||
CollectionAssert.AreNotEqual(bytes, cipherBytes);
|
||||
CollectionAssert.AreEqual(bytes, plainBytes);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ShouldDecryptAesUsingKeyFile()
|
||||
{
|
||||
// arrange
|
||||
string str = "Hello World!";
|
||||
byte[] bytes = CryptographyHelper.GetRandomBytes(32);
|
||||
|
||||
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);
|
||||
|
||||
// assert
|
||||
Assert.AreNotEqual(str, cipherStr);
|
||||
Assert.AreEqual(str, plainStr);
|
||||
CollectionAssert.AreNotEqual(bytes, cipherBytes);
|
||||
CollectionAssert.AreEqual(bytes, plainBytes);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ShouldEncryptTdesUsingKeyFile()
|
||||
{
|
||||
// arrange
|
||||
string str = "Hello World!";
|
||||
byte[] bytes = CryptographyHelper.GetRandomBytes(32);
|
||||
|
||||
string password = File.ReadAllText(_keyFile);
|
||||
|
||||
// act
|
||||
string cipherStr = _cryptoHelper.EncryptTripleDes(str);
|
||||
byte[] cipherBytes = _cryptoHelper.EncryptTripleDes(bytes);
|
||||
|
||||
string plainStr = CryptographyHelper.TripleDesDecrypt(cipherStr, password);
|
||||
byte[] plainBytes = CryptographyHelper.TripleDesDecrypt(cipherBytes, password);
|
||||
|
||||
// assert
|
||||
Assert.AreNotEqual(str, cipherStr);
|
||||
Assert.AreEqual(str, plainStr);
|
||||
CollectionAssert.AreNotEqual(bytes, cipherBytes);
|
||||
CollectionAssert.AreEqual(bytes, plainBytes);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ShouldDecryptTdesUsingKeyFile()
|
||||
{
|
||||
// arrange
|
||||
string str = "Hello World!";
|
||||
byte[] bytes = CryptographyHelper.GetRandomBytes(32);
|
||||
|
||||
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);
|
||||
|
||||
// assert
|
||||
Assert.AreNotEqual(str, cipherStr);
|
||||
Assert.AreEqual(str, plainStr);
|
||||
CollectionAssert.AreNotEqual(bytes, cipherBytes);
|
||||
CollectionAssert.AreEqual(bytes, plainBytes);
|
||||
}
|
||||
|
||||
[GeneratedRegex("[^0-9a-f]")]
|
||||
private static partial Regex RandomStringWithPoolRegex();
|
||||
|
||||
#endregion Instance
|
||||
}
|
||||
}
|
||||
283
test/AMWD.Common.Tests/Utilities/DelayedTaskTest.cs
Normal file
283
test/AMWD.Common.Tests/Utilities/DelayedTaskTest.cs
Normal file
@@ -0,0 +1,283 @@
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using AMWD.Common.Utilities;
|
||||
|
||||
namespace AMWD.Common.Tests.Utilities
|
||||
{
|
||||
[TestClass]
|
||||
public class DelayedTaskTest
|
||||
{
|
||||
[TestMethod]
|
||||
public void ShouldCreateNewDelayedTaskNotStarting()
|
||||
{
|
||||
// arrange
|
||||
int executionCount = 0;
|
||||
var delay = TimeSpan.FromMilliseconds(100);
|
||||
void Action() { executionCount++; }
|
||||
|
||||
// act
|
||||
var cts = new CancellationTokenSource(delay.Add(TimeSpan.FromSeconds(1)));
|
||||
var delayedTask = DelayedTask.Create(Action, delay);
|
||||
SpinWait.SpinUntil(() => executionCount > 0 || cts.IsCancellationRequested);
|
||||
|
||||
// assert
|
||||
Assert.IsNotNull(delayedTask);
|
||||
Assert.AreEqual(delay, delayedTask.Delay);
|
||||
Assert.IsFalse(delayedTask.IsRunning);
|
||||
Assert.IsFalse(delayedTask.IsWaitingToRun);
|
||||
Assert.IsNull(delayedTask.Exception);
|
||||
Assert.AreEqual(0, executionCount);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ShouldCreateNewDelayedTaskStarting()
|
||||
{
|
||||
// arrange
|
||||
int executionCount = 0;
|
||||
var delay = TimeSpan.FromMilliseconds(100);
|
||||
void Action() { executionCount++; }
|
||||
|
||||
// act
|
||||
var cts = new CancellationTokenSource(delay.Add(TimeSpan.FromSeconds(1)));
|
||||
var delayedTask = DelayedTask.Run(Action, delay);
|
||||
SpinWait.SpinUntil(() => executionCount > 0 || cts.IsCancellationRequested);
|
||||
|
||||
// assert
|
||||
Assert.IsNotNull(delayedTask);
|
||||
Assert.AreEqual(delay, delayedTask.Delay);
|
||||
Assert.IsFalse(delayedTask.IsRunning);
|
||||
Assert.IsFalse(delayedTask.IsWaitingToRun);
|
||||
Assert.IsNull(delayedTask.Exception);
|
||||
Assert.AreEqual(1, executionCount);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ShouldRunOnceAfterReset()
|
||||
{
|
||||
// arrange
|
||||
int executionCount = 0;
|
||||
var delay = TimeSpan.FromMilliseconds(100);
|
||||
void Action() { executionCount++; }
|
||||
|
||||
// act
|
||||
var cts = new CancellationTokenSource(delay.Add(TimeSpan.FromSeconds(1)));
|
||||
var delayedTask = DelayedTask.Create(Action, delay);
|
||||
delayedTask.Reset();
|
||||
SpinWait.SpinUntil(() => executionCount > 0 || cts.IsCancellationRequested);
|
||||
|
||||
// assert
|
||||
Assert.IsNotNull(delayedTask);
|
||||
Assert.AreEqual(delay, delayedTask.Delay);
|
||||
Assert.IsFalse(delayedTask.IsRunning);
|
||||
Assert.IsFalse(delayedTask.IsWaitingToRun);
|
||||
Assert.IsNull(delayedTask.Exception);
|
||||
Assert.AreEqual(1, executionCount);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ShouldCancelWaitingTask()
|
||||
{
|
||||
// arrange
|
||||
int executionCount = 0;
|
||||
var delay = TimeSpan.FromMilliseconds(100);
|
||||
void Action() { executionCount++; }
|
||||
|
||||
// act
|
||||
var cts = new CancellationTokenSource(delay.Add(TimeSpan.FromSeconds(1)));
|
||||
var delayedTask = DelayedTask.Run(Action, delay);
|
||||
delayedTask.Cancel();
|
||||
SpinWait.SpinUntil(() => executionCount > 0 || cts.IsCancellationRequested);
|
||||
|
||||
// assert
|
||||
Assert.IsNotNull(delayedTask);
|
||||
Assert.AreEqual(delay, delayedTask.Delay);
|
||||
Assert.IsFalse(delayedTask.IsRunning);
|
||||
Assert.IsFalse(delayedTask.IsWaitingToRun);
|
||||
Assert.IsNull(delayedTask.Exception);
|
||||
Assert.AreEqual(0, executionCount);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public async Task ShouldResetRunningDelay()
|
||||
{
|
||||
// arrange
|
||||
int executionCount = 0;
|
||||
var sw = new Stopwatch();
|
||||
|
||||
var delay = TimeSpan.FromMilliseconds(200);
|
||||
void Action() { sw.Stop(); executionCount++; }
|
||||
|
||||
// act
|
||||
var cts = new CancellationTokenSource(delay.Add(TimeSpan.FromSeconds(1)));
|
||||
sw.Start();
|
||||
var delayedTask = DelayedTask.Run(Action, delay);
|
||||
await Task.Delay(50);
|
||||
delayedTask.Reset();
|
||||
SpinWait.SpinUntil(() => executionCount > 0 || cts.IsCancellationRequested);
|
||||
|
||||
// assert
|
||||
Assert.IsNotNull(delayedTask);
|
||||
Assert.AreEqual(delay, delayedTask.Delay);
|
||||
Assert.IsFalse(delayedTask.IsRunning);
|
||||
Assert.IsFalse(delayedTask.IsWaitingToRun);
|
||||
Assert.IsNull(delayedTask.Exception);
|
||||
Assert.AreEqual(1, executionCount);
|
||||
// delta of 60ms as the precision below 50ms is really bad for System.Timer
|
||||
Assert.AreEqual(250, sw.ElapsedMilliseconds, 60);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public async Task ShouldNotExecutedImmediateOnCreated()
|
||||
{
|
||||
// arrange
|
||||
int executionCount = 0;
|
||||
var sw = new Stopwatch();
|
||||
|
||||
var delay = TimeSpan.FromMilliseconds(200);
|
||||
void Action() { sw.Stop(); executionCount++; }
|
||||
|
||||
// act
|
||||
var cts = new CancellationTokenSource(delay.Add(TimeSpan.FromSeconds(1)));
|
||||
sw.Start();
|
||||
var delayedTask = DelayedTask.Create(Action, delay);
|
||||
await Task.Delay(50);
|
||||
bool isSuccess = delayedTask.ExecutePending();
|
||||
SpinWait.SpinUntil(() => executionCount > 0 || cts.IsCancellationRequested);
|
||||
sw.Stop();
|
||||
|
||||
// assert
|
||||
Assert.IsNotNull(delayedTask);
|
||||
Assert.AreEqual(delay, delayedTask.Delay);
|
||||
Assert.IsFalse(delayedTask.IsRunning);
|
||||
Assert.IsFalse(delayedTask.IsWaitingToRun);
|
||||
Assert.IsNull(delayedTask.Exception);
|
||||
Assert.AreEqual(0, executionCount);
|
||||
// delta of 60ms as the precision below 50ms is really bad for System.Timer
|
||||
Assert.AreEqual(1250, sw.ElapsedMilliseconds, 60);
|
||||
Assert.IsFalse(isSuccess);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public async Task ShouldExecuteImmediateOnExecutePendingWhenRunning()
|
||||
{
|
||||
// arrange
|
||||
int executionCount = 0;
|
||||
var sw = new Stopwatch();
|
||||
|
||||
var delay = TimeSpan.FromMilliseconds(200);
|
||||
void Action() { sw.Stop(); executionCount++; }
|
||||
|
||||
// act
|
||||
var cts = new CancellationTokenSource(delay.Add(TimeSpan.FromSeconds(1)));
|
||||
sw.Start();
|
||||
var delayedTask = DelayedTask.Run(Action, delay);
|
||||
await Task.Delay(50);
|
||||
bool isSuccess = delayedTask.ExecutePending();
|
||||
SpinWait.SpinUntil(() => executionCount > 0 || cts.IsCancellationRequested);
|
||||
|
||||
// assert
|
||||
Assert.IsNotNull(delayedTask);
|
||||
Assert.AreEqual(delay, delayedTask.Delay);
|
||||
Assert.IsFalse(delayedTask.IsRunning);
|
||||
Assert.IsFalse(delayedTask.IsWaitingToRun);
|
||||
Assert.IsNull(delayedTask.Exception);
|
||||
Assert.AreEqual(1, executionCount);
|
||||
// delta of 60ms as the precision below 50ms is really bad for System.Timer
|
||||
Assert.AreEqual(50, sw.ElapsedMilliseconds, 60);
|
||||
Assert.IsTrue(isSuccess);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ShouldReturnAnAwaiter()
|
||||
{
|
||||
// arrange
|
||||
int executionCount = 0;
|
||||
var delay = TimeSpan.FromMilliseconds(100);
|
||||
void Action() { executionCount++; }
|
||||
var delayedTask = DelayedTask.Create(Action, delay);
|
||||
|
||||
// act
|
||||
delayedTask.Reset();
|
||||
var awaiter = delayedTask.GetAwaiter();
|
||||
SpinWait.SpinUntil(() => awaiter.IsCompleted);
|
||||
|
||||
// assert
|
||||
Assert.IsNotNull(delayedTask);
|
||||
Assert.IsNotNull(awaiter);
|
||||
Assert.IsFalse(delayedTask.IsRunning);
|
||||
Assert.IsFalse(delayedTask.IsWaitingToRun);
|
||||
Assert.IsNull(delayedTask.Exception);
|
||||
Assert.AreEqual(1, executionCount);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ShouldHaveAnExceptionSet()
|
||||
{
|
||||
// arrange
|
||||
var delay = TimeSpan.FromMilliseconds(100);
|
||||
static void Action() { throw new Exception("TEST :D"); }
|
||||
|
||||
// act
|
||||
var delayedTask = DelayedTask.Run(Action, delay);
|
||||
|
||||
var awaiter = delayedTask.GetAwaiter();
|
||||
SpinWait.SpinUntil(() => awaiter.IsCompleted);
|
||||
|
||||
// assert
|
||||
Assert.IsNotNull(delayedTask);
|
||||
Assert.IsFalse(delayedTask.IsRunning);
|
||||
Assert.IsFalse(delayedTask.IsWaitingToRun);
|
||||
Assert.IsNotNull(delayedTask.Exception);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ShouldUseExceptionHandler()
|
||||
{
|
||||
// arrange
|
||||
string exceptionText = null;
|
||||
var delay = TimeSpan.FromMilliseconds(100);
|
||||
void Action() { throw new Exception("TEST :D"); }
|
||||
void ExceptionHandler(Exception ex) { exceptionText = ex.Message; }
|
||||
|
||||
// act
|
||||
var delayedTask = DelayedTask.Run(Action, delay)
|
||||
.WithExceptionHandler(ExceptionHandler);
|
||||
|
||||
var awaiter = delayedTask.GetAwaiter();
|
||||
SpinWait.SpinUntil(() => awaiter.IsCompleted);
|
||||
|
||||
// assert
|
||||
Assert.IsNotNull(delayedTask);
|
||||
Assert.IsFalse(delayedTask.IsRunning);
|
||||
Assert.IsFalse(delayedTask.IsWaitingToRun);
|
||||
Assert.IsNotNull(delayedTask.Exception);
|
||||
Assert.AreEqual("TEST :D", exceptionText);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public async Task ShouldReturnNormalTask()
|
||||
{
|
||||
// arrange
|
||||
int executionCount = 0;
|
||||
var delay = TimeSpan.FromMilliseconds(100);
|
||||
void Action() { executionCount++; }
|
||||
var delayedTask = DelayedTask.Create(Action, delay);
|
||||
|
||||
// act
|
||||
delayedTask.Reset();
|
||||
var task = delayedTask.Task;
|
||||
await task;
|
||||
|
||||
// assert
|
||||
Assert.IsNotNull(delayedTask);
|
||||
Assert.IsNotNull(task);
|
||||
Assert.IsInstanceOfType(task, typeof(Task));
|
||||
Assert.IsFalse(delayedTask.IsRunning);
|
||||
Assert.IsFalse(delayedTask.IsWaitingToRun);
|
||||
Assert.AreEqual(1, executionCount);
|
||||
Assert.AreEqual(TaskStatus.RanToCompletion, task.Status);
|
||||
}
|
||||
}
|
||||
}
|
||||
130
test/AMWD.Common.Tests/Utilities/DelayedTaskWithResultTest.cs
Normal file
130
test/AMWD.Common.Tests/Utilities/DelayedTaskWithResultTest.cs
Normal file
@@ -0,0 +1,130 @@
|
||||
using System;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using AMWD.Common.Utilities;
|
||||
|
||||
namespace AMWD.Common.Tests.Utilities
|
||||
{
|
||||
[TestClass]
|
||||
public class DelayedTaskWithResultTest
|
||||
{
|
||||
[TestMethod]
|
||||
public void ShouldCreateNewDelayedTaskNotStarting()
|
||||
{
|
||||
// arrange
|
||||
int executionCount = 0;
|
||||
var delay = TimeSpan.FromMilliseconds(100);
|
||||
int[] Function() { executionCount++; return [42, 21]; }
|
||||
|
||||
// act
|
||||
var cts = new CancellationTokenSource(delay.Add(TimeSpan.FromSeconds(1)));
|
||||
var delayedTaskWithResult = DelayedTask.Create(Function, delay);
|
||||
SpinWait.SpinUntil(() => executionCount > 0 || cts.IsCancellationRequested);
|
||||
|
||||
// assert
|
||||
Assert.IsNotNull(delayedTaskWithResult);
|
||||
Assert.AreEqual(delay, delayedTaskWithResult.Delay);
|
||||
Assert.IsFalse(delayedTaskWithResult.IsRunning);
|
||||
Assert.IsFalse(delayedTaskWithResult.IsWaitingToRun);
|
||||
Assert.IsNull(delayedTaskWithResult.Exception);
|
||||
Assert.AreEqual(0, executionCount);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[System.Diagnostics.CodeAnalysis.SuppressMessage("Performance", "CA1861")]
|
||||
public async Task ShouldCreateNewDelayedTaskStarting()
|
||||
{
|
||||
// arrange
|
||||
int executionCount = 0;
|
||||
var delay = TimeSpan.FromMilliseconds(100);
|
||||
int[] Function() { executionCount++; return [42, 21]; }
|
||||
|
||||
// act
|
||||
var delayedTaskWithResult = DelayedTask.Run(Function, delay);
|
||||
int[] result = await delayedTaskWithResult;
|
||||
|
||||
// assert
|
||||
Assert.IsNotNull(delayedTaskWithResult);
|
||||
Assert.AreEqual(delay, delayedTaskWithResult.Delay);
|
||||
Assert.IsFalse(delayedTaskWithResult.IsRunning);
|
||||
Assert.IsFalse(delayedTaskWithResult.IsWaitingToRun);
|
||||
Assert.IsNull(delayedTaskWithResult.Exception);
|
||||
Assert.AreEqual(1, executionCount);
|
||||
CollectionAssert.AreEqual(new[] { 42, 21 }, result);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ShouldHaveAnExceptionSet()
|
||||
{
|
||||
// arrange
|
||||
var delay = TimeSpan.FromMilliseconds(100);
|
||||
#pragma warning disable CS0162 // Unreachable Code detected.
|
||||
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 awaiter = delayedTaskWithResult.GetAwaiter();
|
||||
SpinWait.SpinUntil(() => awaiter.IsCompleted);
|
||||
|
||||
// assert
|
||||
Assert.IsNotNull(delayedTaskWithResult);
|
||||
Assert.IsFalse(delayedTaskWithResult.IsRunning);
|
||||
Assert.IsFalse(delayedTaskWithResult.IsWaitingToRun);
|
||||
Assert.IsNotNull(delayedTaskWithResult.Exception);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ShouldUseExceptionHandler()
|
||||
{
|
||||
// arrange
|
||||
string exceptionText = null;
|
||||
var delay = TimeSpan.FromMilliseconds(100);
|
||||
#pragma warning disable CS0162 // Unreachable Code detected.
|
||||
static int[] Function() { throw new Exception("TEST :D"); return [42, 21]; }
|
||||
#pragma warning restore CS0162 // Unreachable Code detected
|
||||
void ExceptionHandler(Exception ex) { exceptionText = ex.Message; }
|
||||
|
||||
// act
|
||||
var delayedTaskWithResult = DelayedTask.Run(Function, delay)
|
||||
.WithExceptionHandler(ExceptionHandler);
|
||||
|
||||
var awaiter = delayedTaskWithResult.GetAwaiter();
|
||||
SpinWait.SpinUntil(() => awaiter.IsCompleted);
|
||||
|
||||
// assert
|
||||
Assert.IsNotNull(delayedTaskWithResult);
|
||||
Assert.IsFalse(delayedTaskWithResult.IsRunning);
|
||||
Assert.IsFalse(delayedTaskWithResult.IsWaitingToRun);
|
||||
Assert.IsNotNull(delayedTaskWithResult.Exception);
|
||||
Assert.AreEqual("TEST :D", exceptionText);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[System.Diagnostics.CodeAnalysis.SuppressMessage("Performance", "CA1861")]
|
||||
public async Task ShouldReturnNormalTask()
|
||||
{
|
||||
// arrange
|
||||
int executionCount = 0;
|
||||
var delay = TimeSpan.FromMilliseconds(100);
|
||||
int[] Function() { executionCount++; return [42, 21]; }
|
||||
var delayedTaskWithResult = DelayedTask.Create(Function, delay);
|
||||
|
||||
// act
|
||||
delayedTaskWithResult.Reset();
|
||||
var task = delayedTaskWithResult.Task;
|
||||
int[] result = await task;
|
||||
|
||||
// assert
|
||||
Assert.IsNotNull(delayedTaskWithResult);
|
||||
Assert.IsNotNull(task);
|
||||
Assert.IsInstanceOfType(task, typeof(Task));
|
||||
Assert.IsFalse(delayedTaskWithResult.IsRunning);
|
||||
Assert.IsFalse(delayedTaskWithResult.IsWaitingToRun);
|
||||
Assert.AreEqual(1, executionCount);
|
||||
Assert.AreEqual(TaskStatus.RanToCompletion, task.Status);
|
||||
CollectionAssert.AreEqual(new int[] { 42, 21 }, result);
|
||||
}
|
||||
}
|
||||
}
|
||||
27
test/AMWD.Common.Tests/Utils/CryptographyHelperSaltMock.cs
Normal file
27
test/AMWD.Common.Tests/Utils/CryptographyHelperSaltMock.cs
Normal file
@@ -0,0 +1,27 @@
|
||||
using System;
|
||||
using System.Security.Cryptography;
|
||||
using ReflectionMagic;
|
||||
|
||||
namespace AMWD.Common.Tests.Utils
|
||||
{
|
||||
[System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage]
|
||||
internal class CryptographyHelperSaltMock : IDisposable
|
||||
{
|
||||
private readonly int _saltLength;
|
||||
|
||||
private CryptographyHelperSaltMock(int saltLength)
|
||||
{
|
||||
_saltLength = typeof(CryptographyHelper).AsDynamicType()._saltLength;
|
||||
SetSaltLength(saltLength);
|
||||
}
|
||||
|
||||
public static IDisposable Create(int saltLength)
|
||||
=> new CryptographyHelperSaltMock(saltLength);
|
||||
|
||||
public void Dispose()
|
||||
=> SetSaltLength(_saltLength);
|
||||
|
||||
private static void SetSaltLength(int length)
|
||||
=> typeof(CryptographyHelper).AsDynamicType()._saltLength = length;
|
||||
}
|
||||
}
|
||||
11
test/AMWD.Common.Tests/Utils/CustomMultipleAttribute.cs
Normal file
11
test/AMWD.Common.Tests/Utils/CustomMultipleAttribute.cs
Normal file
@@ -0,0 +1,11 @@
|
||||
using System;
|
||||
|
||||
namespace AMWD.Common.Tests.Utils
|
||||
{
|
||||
[System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage]
|
||||
[AttributeUsage(AttributeTargets.All, AllowMultiple = true)]
|
||||
internal class CustomMultipleAttribute(string name) : Attribute
|
||||
{
|
||||
public string Name { get; set; } = name;
|
||||
}
|
||||
}
|
||||
53
test/AMWD.Common.Tests/Utils/JsonTestClass.cs
Normal file
53
test/AMWD.Common.Tests/Utils/JsonTestClass.cs
Normal file
@@ -0,0 +1,53 @@
|
||||
using System;
|
||||
|
||||
namespace AMWD.Common.Tests.Utils
|
||||
{
|
||||
[System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage]
|
||||
internal class JsonTestClass
|
||||
{
|
||||
public string StringValue { get; set; } = "Hello World!";
|
||||
|
||||
public bool IsBoolTrue { get; set; } = true;
|
||||
|
||||
public float FloatValue { get; set; } = 12.34f;
|
||||
|
||||
public double DoubleValue { get; set; } = 21.42;
|
||||
|
||||
public decimal DecimalValue { get; set; } = 42.21m;
|
||||
|
||||
public DateTime LocalTimestamp { get; set; } = new(2021, 11, 16, 20, 15, 34, DateTimeKind.Local);
|
||||
|
||||
public DateTime UtcTimestamp { get; set; } = new(2021, 11, 16, 20, 15, 34, DateTimeKind.Utc);
|
||||
|
||||
public JsonTestSubClass Object { get; set; } = new();
|
||||
}
|
||||
|
||||
[System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage]
|
||||
internal class JsonTestSubClass
|
||||
{
|
||||
public int IntegerValue { get; set; } = 42;
|
||||
|
||||
public string StringValue { get; set; } = "Foo-Bar";
|
||||
}
|
||||
|
||||
[System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage]
|
||||
internal class JsonErrorClass
|
||||
{
|
||||
private int? _number;
|
||||
|
||||
public int Number
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_number.HasValue)
|
||||
return _number.Value;
|
||||
|
||||
throw new Exception("Null value");
|
||||
}
|
||||
set
|
||||
{
|
||||
_number = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
26
test/AMWD.Common.Tests/Utils/TimeZoneInfoLocalMock.cs
Normal file
26
test/AMWD.Common.Tests/Utils/TimeZoneInfoLocalMock.cs
Normal file
@@ -0,0 +1,26 @@
|
||||
using System;
|
||||
using ReflectionMagic;
|
||||
|
||||
namespace AMWD.Common.Tests.Utils
|
||||
{
|
||||
[System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage]
|
||||
internal class TimeZoneInfoLocalMock : IDisposable
|
||||
{
|
||||
private readonly TimeZoneInfo _localTimeZoneInfo;
|
||||
|
||||
private TimeZoneInfoLocalMock(TimeZoneInfo timeZoneInfo)
|
||||
{
|
||||
_localTimeZoneInfo = TimeZoneInfo.Local;
|
||||
SetLocalTimeZone(timeZoneInfo);
|
||||
}
|
||||
|
||||
public static IDisposable Create(TimeZoneInfo mockTimeZoneInfo)
|
||||
=> new TimeZoneInfoLocalMock(mockTimeZoneInfo);
|
||||
|
||||
public void Dispose()
|
||||
=> SetLocalTimeZone(_localTimeZoneInfo);
|
||||
|
||||
private static void SetLocalTimeZone(TimeZoneInfo timeZoneInfo)
|
||||
=> typeof(TimeZoneInfo).AsDynamicType().s_cachedData._localTimeZone = timeZoneInfo;
|
||||
}
|
||||
}
|
||||
25
test/Directory.Build.props
Normal file
25
test/Directory.Build.props
Normal file
@@ -0,0 +1,25 @@
|
||||
<Project>
|
||||
<PropertyGroup>
|
||||
<IsPackable>false</IsPackable>
|
||||
<IsTestProject>true</IsTestProject>
|
||||
<CollectCoverage>true</CollectCoverage>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="coverlet.msbuild" Version="6.0.2">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.10.0" />
|
||||
<PackageReference Include="Moq" Version="4.20.70" />
|
||||
<PackageReference Include="MSTest.TestAdapter" Version="3.4.3" />
|
||||
<PackageReference Include="MSTest.TestFramework" Version="3.4.3" />
|
||||
<PackageReference Include="ReflectionMagic" Version="5.0.1" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Using Include="Microsoft.VisualStudio.TestTools.UnitTesting" />
|
||||
</ItemGroup>
|
||||
|
||||
<Import Project="$([MSBuild]::GetPathOfFileAbove('Directory.Build.props', '$(MSBuildThisFileDirectory)../'))" />
|
||||
</Project>
|
||||
Reference in New Issue
Block a user