(feat): adding common extension methods and tests

This commit is contained in:
Maksym Sadovnychyy 2022-08-06 23:16:25 +02:00
parent 522a9d4a43
commit c91a4f4776
23 changed files with 1394 additions and 1 deletions

View File

@ -3,7 +3,7 @@ using MongoDB.Bson.IO;
using MongoDB.Bson.Serialization;
using Core.Abstractions;
namespace PecMgr.DataProviders.Converters {
namespace DataProviders.Converters {
public class EnumerationSerializer<T> : IBsonSerializer<T> where T : Enumeration {
private T Deserialize(IBsonReader reader) => Enumeration.FromValue<T>(BsonSerializer.Deserialize<int>(reader));

View File

@ -0,0 +1,14 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<RootNamespace>$(MSBuildProjectName.Replace(" ", "_"))</RootNamespace>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Http.Abstractions" Version="2.2.0" />
</ItemGroup>
</Project>

View File

@ -0,0 +1,45 @@
using Microsoft.AspNetCore.Http;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Extensions {
public static class HttpRequestExtensions {
/// <summary>
///
/// </summary>
/// <param name="request"></param>
/// <param name="name"></param>
/// <returns></returns>
public static List<string> GetHeader(this HttpRequest request, string name) {
var headers = request.Headers[name].ToList();
return headers != null ? headers : new List<string>();
}
/// <summary>
/// Return clean JWT Bearer token from Authorisation Header
/// </summary>
public static string? GeBearerToken(this HttpRequest request) {
var header = request.GetHeader("Authorization").FirstOrDefault();
return header !=null
? header.Replace("Bearer ", "")
: default;
}
/// <summary>
/// Returns JWT Bearer token Vault path from custom AuthorizationPath Header
/// </summary>
/// <param name="request"></param>
/// <returns></returns>
public static string? GetBearerPath(this HttpRequest request) {
var header = request.GetHeader("AuthorizationPath").FirstOrDefault();
return header;
}
}
}

View File

@ -0,0 +1,66 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace ExtensionMethods {
public static class IEquatableExtensions {
/// <summary>
///
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="item1"></param>
/// <param name="item2"></param>
/// <returns></returns>
public static bool Eq<T>(this IEquatable<T>? item1, T? item2) {
if ((item1 == null && item2 != null) || (item1 != null && item2 == null))
return false;
if (item1 == null && item2 == null)
return true;
if (item1 != null && item2 != null) {
var result = item1.Equals(item2);
return result;
}
return false;
}
/// <summary>
///
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="list1"></param>
/// <param name="list2"></param>
/// <returns></returns>
public static bool EnumerableEq<T>(this IEnumerable<IEquatable<T>>? list1, IEnumerable<IEquatable<T>>? list2) {
if ((list1 == null && list2 != null) || (list1 != null && list2 == null))
return false;
if (list1 == null && list2 == null)
return true;
if (list1 != null && list2 != null && list1.Count() == list2.Count()) {
var diffDic = list2.GroupBy(x => x.GetHashCode()).ToDictionary(g => g.Key, g => g.Count());
for (int i = 0; i < list1.Count(); i++) {
var obj = (T)list1.ElementAt(i);
var objHash = obj.GetHashCode();
if (diffDic.ContainsKey(objHash))
diffDic[objHash] = diffDic[objHash] - 1;
else
diffDic.Add(objHash, -1);
}
return !diffDic.Any(x => x.Value != 0);
}
return false;
}
}
}

View File

@ -0,0 +1,20 @@
using System.Text.Json;
using System.Text.Json.Serialization;
namespace ExtensionMethods {
public static class ObjectExtensions {
/// <summary>
/// Converts object to json string
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="obj"></param>
/// <returns></returns>
public static string ToJson<T>(this T obj) {
return JsonSerializer.Serialize(obj, new JsonSerializerOptions {
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull
});
}
}
}

View File

@ -0,0 +1,345 @@
using System;
using System.Text;
using System.Collections.Generic;
using System.Linq;
using System.Text.RegularExpressions;
using System.Globalization;
using System.Security.Cryptography;
using System.ComponentModel.DataAnnotations;
using System.Text.Json;
namespace ExtensionMethods {
public static class StringExtensions {
/// <summary>
/// SQL Like implementation
/// </summary>
/// <param name="text"></param>
/// <param name="wildcardedText"></param>
/// <returns></returns>
public static bool Like(this string text, string wildcardedText) {
text = text ?? "";
wildcardedText = wildcardedText ?? "";
return Regex.IsMatch(text, wildcardedText.WildcardToRegular(), RegexOptions.IgnoreCase | RegexOptions.Multiline);
}
/// <summary>
/// Converts wildcarded string tgo regex
/// </summary>
/// <param name="value"></param>
/// <returns></returns>
private static string WildcardToRegular(this string value) =>
$"^{Regex.Escape(value).Replace("\\?", ".").Replace("\\*", ".*")}$";
/// <summary>
/// VB.Net Left implementation
/// </summary>
/// <param name="s"></param>
/// <param name="count"></param>
/// <returns></returns>
public static string Left(this string s, int count) {
return s.Substring(0, count);
}
/// <summary>
/// VB.Net Right implementation
/// </summary>
/// <param name="s"></param>
/// <param name="count"></param>
/// <returns></returns>
public static string Right(this string s, int count) {
return s.Substring(s.Length - count, count);
}
/// <summary>
/// VB.Net Mid implementation
/// </summary>
/// <param name="s"></param>
/// <param name="index"></param>
/// <param name="count"></param>
/// <returns></returns>
public static string Mid(this string s, int index, int count) {
return s.Substring(index, count);
}
/// <summary>
/// VB.Net ToInteger implementation
/// </summary>
/// <param name="s"></param>
/// <returns></returns>
public static int ToInteger(this string s) {
var integerValue = 0;
int.TryParse(s, out integerValue);
return integerValue;
}
/// <summary>
/// VB.Net IsInteger implementations
/// </summary>
/// <param name="s"></param>
/// <returns></returns>
public static bool IsInteger(this string s) => new Regex("^-[0-9]+$|^[0-9]+$").Match(s).Success;
public static StringBuilder Prepend(this StringBuilder sb, string content) => sb.Insert(0, content);
public static T ToEnum<T>(this string input) where T : struct {
var result = default(T);
if (string.IsNullOrWhiteSpace(input))
return result;
bool parseSucceeded = Enum.TryParse(input, true, out result);
if (parseSucceeded)
return result;
// se fallisce il parse per valore proviamo ad usare il DisplayAttribute.Name
var enumType = typeof(T);
foreach (T enumItem in Enum.GetValues(enumType)) {
var att = enumType.GetMember(enumItem.ToString())[0]
.GetCustomAttributes(typeof(DisplayAttribute), false).SingleOrDefault();
var displayName = ((DisplayAttribute)att).GetName();
if (input.Equals(displayName, StringComparison.InvariantCultureIgnoreCase))
return enumItem;
}
throw new NotSupportedException($"Cannot parse the value '{input}' for {enumType}");
}
public static T? ToNullableEnum<T>(this string input) where T : struct => !string.IsNullOrWhiteSpace(input) ? input.ToEnum<T>() : default(T?);
public static string ToNull(this string s) => !string.IsNullOrWhiteSpace(s) ? s : null;
public static string NullIfEmptyString(this string s) => s.ToNull();
public static long ToLong(this string s) {
if (!long.TryParse(s, NumberStyles.Any, NumberFormatInfo.InvariantInfo, out long result))
result = !string.IsNullOrWhiteSpace(s) ? s.ToUpper().GetHashCode() : default;
return result;
}
public static long? ToNullableLong(this string s) => !string.IsNullOrWhiteSpace(s) ? s.ToLong() : default(long?);
public static int ToInt(this string s) {
if (!int.TryParse(s, NumberStyles.Any, NumberFormatInfo.InvariantInfo, out int result))
result = !string.IsNullOrWhiteSpace(s) ? s.ToUpper().GetHashCode() : default;
return result;
}
public static int? ToNullableInt(this string s) => !string.IsNullOrWhiteSpace(s) ? s.ToInt() : default(int?);
public static uint ToUint(this string s) {
if (!uint.TryParse(s, NumberStyles.Any, NumberFormatInfo.InvariantInfo, out uint result))
result = !string.IsNullOrWhiteSpace(s) ? (uint)s.ToUpper().GetHashCode() : default;
return result;
}
public static uint? ToNullableUint(this string s) => !string.IsNullOrWhiteSpace(s) ? s.ToUint() : default(uint?);
public static decimal ToDecimal(this string s) {
if (!int.TryParse(s, NumberStyles.Any, NumberFormatInfo.InvariantInfo, out int result))
result = !string.IsNullOrWhiteSpace(s) ? s.ToUpper().GetHashCode() : default;
return result;
}
public static decimal? ToNullableDecimal(this string s) => !string.IsNullOrWhiteSpace(s) ? s.ToDecimal() : default(decimal?);
public static double ToDouble(this string s) {
if (!double.TryParse(s, NumberStyles.Any, NumberFormatInfo.InvariantInfo, out double result))
result = !string.IsNullOrWhiteSpace(s) ? s.ToUpper().GetHashCode() : default;
return result;
}
public static double? ToNullableDouble(this string s) => !string.IsNullOrWhiteSpace(s) ? s.ToDouble() : default(double?);
#region DateTime
public static DateTime ToDate(this string s, string[] formats) {
return DateTime.TryParseExact(s, formats, CultureInfo.InvariantCulture, DateTimeStyles.None, out DateTime datetime)
? DateTime.SpecifyKind(datetime, DateTimeKind.Utc)
: throw new ApplicationException($"the date [{s}] is not in required format: [{formats[0]}]");
}
public static DateTime ToDate(this string s) => s.ToDate(new string[] { "dd/MM/yyyy" });
public static DateTime? ToNullableDate(this string s) => !string.IsNullOrEmpty(s) ? s.ToDate() : default(DateTime?);
public static DateTime? ToNullableDate(this string s, string[] formats) => !string.IsNullOrEmpty(s) ? s.ToDate(formats) : default(DateTime?);
public static DateTime ToDateTime(this string s, string[] formats) {
if (s == "Now")
return DateTime.Now;
else if (s == "Today")
return DateTime.Today;
DateTime result;
if (!DateTime.TryParseExact(s, formats, new DateTimeFormatInfo(), DateTimeStyles.None, out result))
throw new ApplicationException(string.Format("unable to parse exact date from value [{0}] with formats [{1}]", s, string.Join(", ", formats)));
return DateTime.SpecifyKind(result, DateTimeKind.Utc);
}
public static DateTime ToDateTime(this string s) => s.ToDateTime(new string[] { "dd/MM/yyyy", "dd/MM/yyyy HH:mm:ss", "dd/MM/yyyy HH:mm", "yyyy-MM-dd'T'HH:mm:ss'Z'" });
public static DateTime? ToNullableDateTime(this string s) => !string.IsNullOrWhiteSpace(s) ? s.ToDateTime() : default(DateTime?);
public static DateTime? ToNullableDateTime(this string s, string[] formats) => !string.IsNullOrWhiteSpace(s) ? s.ToDateTime(formats) : default(DateTime?);
#endregion
public static bool ToBool(this string s) => new string[] { "ok", "yes", "y", "true", "1" }.Any(x => string.Equals(x, s, StringComparison.InvariantCultureIgnoreCase));
public static bool? ToNullableBool(this string s) => !string.IsNullOrWhiteSpace(s) ? s.ToBool() : default(bool?);
public static Guid ToGuid(this string text) {
if (Guid.TryParse(text, out var value))
return value;
using var md5 = MD5.Create();
byte[] hash = md5.ComputeHash(Encoding.Default.GetBytes(text.ToUpper()));
return new Guid(hash);
}
public static Guid? ToNullableGuid(this string s) => !string.IsNullOrWhiteSpace(s) ? s.ToGuid() : default(Guid?);
public static string[] StringSplit(this string s, char c) {
return s.Split(c).Select(x => x.Trim()).ToArray();
}
/// <summary>
///
/// </summary>
/// <param name="s"></param>
/// <returns></returns>
public static string ToTitle(this string s) {
if (!string.IsNullOrWhiteSpace(s)) {
var tmp = s.ToCharArray();
tmp[0] = char.ToUpper(tmp[0]);
s = new string(tmp);
}
return s;
}
public static IEnumerable<Uri> ExtractUrls(this string s) {
var urls = new List<Uri>();
foreach (Match match in Regex.Matches(s, @"(http|ftp|https):\/\/([\w_-]+(?:(?:\.[\w_-]+)+))([\w.,@?^=%&:\/~+#-]*[\w@?^=%&\/~+#-])")) {
if (Uri.TryCreate(match.Value, UriKind.Absolute, out Uri uri)) {
urls.Add(uri);
}
}
return urls.Distinct();
}
public static string Format(this string s, params object[] args) => string.Format(s, args);
/// <summary>
/// Adds elipsis
/// </summary>
/// <param name="s"></param>
/// <param name="length"></param>
/// <returns></returns>
public static string Excerpt(this string s, int length = 60) =>
string.IsNullOrEmpty(s) || s.Length < length ? s : s.Substring(0, length - 3) + "...";
/// <summary>
/// Converts JSON string to object
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="s"></param>
/// <returns></returns>
public static T? ToObject<T>(this string s) => JsonSerializer.Deserialize<T>(s, new JsonSerializerOptions {
PropertyNameCaseInsensitive = true
});
/// <summary>
/// Adapted from .Net [EmailAddres] model validation attribute
/// https://referencesource.microsoft.com/#System.ComponentModel.DataAnnotations/DataAnnotations/EmailAddressAttribute.cs,17f9a2457ddc8d93
/// </summary>
/// <param name="s"></param>
/// <returns></returns>
public static bool IsValidEmail(this string? s) {
Regex CreateRegEx() {
const string pattern = @"^((([a-z]|\d|[!#\$%&'\*\+\-\/=\?\^_`{\|}~]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+(\.([a-z]|\d|[!#\$%&'\*\+\-\/=\?\^_`{\|}~]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+)*)|((\x22)((((\x20|\x09)*(\x0d\x0a))?(\x20|\x09)+)?(([\x01-\x08\x0b\x0c\x0e-\x1f\x7f]|\x21|[\x23-\x5b]|[\x5d-\x7e]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(\\([\x01-\x09\x0b\x0c\x0d-\x7f]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]))))*(((\x20|\x09)*(\x0d\x0a))?(\x20|\x09)+)?(\x22)))@((([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.)+(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.?$";
const RegexOptions options = RegexOptions.Compiled | RegexOptions.IgnoreCase | RegexOptions.ExplicitCapture;
// Set explicit regex match timeout, sufficient enough for email parsing
// Unless the global REGEX_DEFAULT_MATCH_TIMEOUT is already set
TimeSpan matchTimeout = TimeSpan.FromSeconds(2);
try {
if (AppDomain.CurrentDomain.GetData("REGEX_DEFAULT_MATCH_TIMEOUT") == null) {
return new Regex(pattern, options, matchTimeout);
}
}
catch {
// Fallback on error
}
// Legacy fallback (without explicit match timeout)
return new Regex(pattern, options);
}
// This attribute provides server-side email validation equivalent to jquery validate,
// and therefore shares the same regular expression. See unit tests for examples.
var _regex = CreateRegEx();
if (s == null)
return false;
return s != null && _regex.Match(s).Length > 0;
}
/// <summary>
/// Convert HTML string to Plain Text without external libraries
/// https://docs.microsoft.com/en-us/answers/questions/594274/convert-html-text-to-plain-text-in-c.html
/// </summary>
/// <param name="htmlCode"></param>
/// <returns></returns>
public static string HTMLToPlainText(this string htmlCode) {
// Remove new lines since they are not visible in HTML
htmlCode = htmlCode.Replace("\n", " ");
// Remove tab spaces
htmlCode = htmlCode.Replace("\t", " ");
// Remove multiple white spaces from HTML
htmlCode = Regex.Replace(htmlCode, "\\s+", " ");
// Remove HEAD tag
htmlCode = Regex.Replace(htmlCode, "<head.*?</head>", "", RegexOptions.IgnoreCase | RegexOptions.Singleline);
// Remove any JavaScript
htmlCode = Regex.Replace(htmlCode, "<script.*?</script>", "", RegexOptions.IgnoreCase | RegexOptions.Singleline);
// Replace special characters like &, <, >, " etc.
StringBuilder sbHTML = new StringBuilder(htmlCode);
// Note: There are many more special characters, these are just
// most common. You can add new characters in this arrays if needed
string[] OldWords = {"&nbsp;", "&amp;", "&quot;", "&lt;", "&gt;", "&reg;", "&copy;", "&bull;", "&trade;","&#39;"};
string[] NewWords = { " ", "&", "\"", "<", ">", "®", "©", "•", "™", "\'" };
for (int i = 0; i < OldWords.Length; i++) {
sbHTML.Replace(OldWords[i], NewWords[i]);
}
// Check if there are line breaks (<br>) or paragraph (<p>)
sbHTML.Replace("<br>", "\n<br>");
sbHTML.Replace("<br ", "\n<br ");
sbHTML.Replace("<p ", "\n<p ");
// Finally, remove all HTML tags and return plain text
var plainText = Regex.Replace(sbHTML.ToString(), "<[^>]*>", "");
// Remove external new lines
plainText = plainText.Trim();
return plainText;
}
}
}

View File

@ -0,0 +1,61 @@
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Xunit;
using CoreTests.Core;
namespace Tests.CoreTests.Abstractions {
[TestCaseOrderer(PriorityOrderer.Name, PriorityOrderer.Assembly)]
public abstract class ConfigurationBase {
protected IConfiguration _configuration;
protected ServiceCollection ServiceCollection = new ServiceCollection();
protected ServiceProvider ServiceProvider { get => ServiceCollection.BuildServiceProvider(); }
public ConfigurationBase() {
_configuration = InitConfig();
ConfigureServices(ServiceCollection);
}
protected abstract void ConfigureServices(IServiceCollection services);
private IConfiguration InitConfig() {
var aspNetCoreEnvironment = Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT");
var currentDirectory = Directory.GetCurrentDirectory();
var configurationBuilder = new ConfigurationBuilder()
.SetBasePath(Directory.GetCurrentDirectory())
.AddEnvironmentVariables();
if (!string.IsNullOrWhiteSpace(aspNetCoreEnvironment) && new FileInfo(Path.Combine(currentDirectory, $"appsettings.{aspNetCoreEnvironment}.json")).Exists)
configurationBuilder.AddJsonFile($"appsettings.{aspNetCoreEnvironment}.json", true);
else if (new FileInfo(Path.Combine(currentDirectory, "appsettings.json")).Exists)
configurationBuilder.AddJsonFile("appsettings.json", true, true);
else
throw new FileNotFoundException($"Unable to find appsetting.json in {currentDirectory}");
//var builtConfig = configurationBuilder.Build();
//var vaultOptions = builtConfig.GetSection("Vault");
//configurationBuilder.AddVault(options => {
// options.Address = vaultOptions["Address"];
// options.UnsealKeys = vaultOptions.GetSection("UnsealKeys").Get<List<string>>();
// options.AuthMethod = EnumerationStringId.FromValue<AuthenticationMethod>(vaultOptions["AuthMethod"]);
// options.AppRoleAuthMethod = vaultOptions.GetSection("AppRoleAuthMethod").Get<AppRoleAuthMethod>();
// options.TokenAuthMethod = vaultOptions.GetSection("TokenAuthMethod").Get<TokenAuthMethod>();
// options.MountPath = vaultOptions["MountPath"];
// options.SecretType = vaultOptions["SecretType"];
// options.ConfigurationMappings = vaultOptions.GetSection("ConfigurationMappings").Get<Dictionary<string, string>>();
//});
return configurationBuilder.Build();
}
}
}

View File

@ -0,0 +1,23 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Tests.CoreTests.Attributes
{
/// <summary>
/// Indicates the priority value which will be assigned
/// to tests in this class which don't have a <see cref="PriorityAttribute"/>.
/// </summary>
[AttributeUsage(AttributeTargets.Class, AllowMultiple = false)]
public class DefaultPriorityAttribute : Attribute
{
public DefaultPriorityAttribute(int priority)
{
Priority = priority;
}
public int Priority { get; private set; }
}
}

View File

@ -0,0 +1,27 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Tests.CoreTests.Attributes
{
/// <summary>
/// <para>Indicates relative priority of tests for execution. Tests with the same
/// priority are run in alphabetical order. </para>
///
/// <para>Tests with no priority attribute
/// are assigned a priority of <see cref="int.MaxValue"/>,
/// unless the class has a <see cref="DefaultPriorityAttribute"/>.</para>
/// </summary>
[AttributeUsage(AttributeTargets.Method, AllowMultiple = false)]
public class PriorityAttribute : Attribute
{
public PriorityAttribute(int priority)
{
Priority = priority;
}
public int Priority { get; private set; }
}
}

View File

@ -0,0 +1,26 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<RootNamespace>$(MSBuildProjectName.Replace(" ", "_"))</RootNamespace>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Configuration" Version="6.0.1" />
<PackageReference Include="Microsoft.Extensions.Configuration.EnvironmentVariables" Version="6.0.1" />
<PackageReference Include="Microsoft.Extensions.Configuration.FileExtensions" Version="6.0.0" />
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="6.0.0" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="6.0.0" />
<PackageReference Include="Microsoft.Extensions.Logging" Version="6.0.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.11.0" />
<PackageReference Include="MongoDB.Driver" Version="2.17.1" />
<PackageReference Include="xunit" Version="2.4.1" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\DataProviders\DataProviders.csproj" />
</ItemGroup>
</Project>

View File

@ -0,0 +1,19 @@
//using DomainResults.Common;
//using Core.Domain;
//using MsgProvider;
//namespace CoreTests.FakeServices {
// public class MessageFakeService : IMessageService, IDisposable {
// public IDomainResult Connect(MailServerConnection conn, string userName, string password) {
// return IDomainResult.Success();
// }
// public IDomainResult Send(byte[] bytes) {
// return IDomainResult.Success();
// }
// public void Dispose() { }
// }
////}

View File

@ -0,0 +1,70 @@
//using DomainResults.Common;
//using Microsoft.Extensions.Logging;
//using Core.Enumerations;
//using ObjectStorageProvider;
//namespace CoreTests.FakeServices {
// public class ObjectStorageFakeService : IObjectStorageFileService {
// private readonly ILogger<ObjectStorageFakeService> _logger;
// private Dictionary<string, Dictionary<string, byte[]>> _collection;
// /// <summary>
// /// Dictionary<bucketName, Dictionary<objectName, content)>
// /// </summary>
// /// <param name="logger"></param>
// /// <param name="collection">Dictionary<objectName, (bucketName, content)></param>
// public ObjectStorageFakeService(
// ILogger<ObjectStorageFakeService> logger,
// Dictionary<string, Dictionary<string, byte[]>> collection
// ) {
// _logger = logger;
// _collection = collection;
// }
// public (byte[]?, IDomainResult) FileDownload(string bucketName, string objectName) =>
// FileDownloadAsync(bucketName, objectName).Result;
// public Task<(byte[]?, IDomainResult)> FileDownloadAsync(string bucketName, string objectName) =>
// Task.Run(() => {
// Dictionary<string, byte[]>? bucket;
// _collection.TryGetValue(bucketName, out bucket);
// if (bucket == null)
// return IDomainResult.Failed<byte[]?>();
// byte[]? content;
// bucket.TryGetValue(objectName, out content);
// if (content == null)
// return IDomainResult.Failed<byte[]?>();
// return IDomainResult.Success(content);
// });
// public (string?, IDomainResult) FileUpload(string bucketName, byte[] bytes, string contentType, string location = "us-east-1", bool force = false) {
// throw new NotImplementedException();
// }
// public (string?, IDomainResult) FileUpload(string bucketName, byte[] bytes, FileExtension fileExtension, string location = "us-east-1", bool force = false) =>
// FileUploadAsync(bucketName, bytes, fileExtension, location, force).Result;
// public Task<(string?, IDomainResult)> FileUploadAsync(string bucketName, byte[] bytes, string contentType, string location = "us-east-1", bool force = false) {
// throw new NotImplementedException();
// }
// public Task<(string?, IDomainResult)> FileUploadAsync(string bucketName, byte[] bytes, FileExtension fileExtension, string location = "us-east-1", bool force = false) =>
// Task.Run(() => {
// var objName = $"{Guid.NewGuid()}{fileExtension.Id}";
// if (!_collection.ContainsKey(bucketName))
// _collection.Add(bucketName, new Dictionary<string, byte[]>());
// _collection[bucketName].Add(objName, bytes);
// return IDomainResult.Success<string?>(objName);
// });
// }
//}

View File

@ -0,0 +1,49 @@
using Microsoft.Extensions.Logging;
using MongoDB.Driver;
using DataProviders;
namespace CoreTests.FakeServices {
public class SessionFakeService : ISessionService {
private readonly ILogger<SessionFakeService> _logger;
private List<Guid> _sessions;
public SessionFakeService(
ILogger<SessionFakeService> logger
) {
_logger = logger;
_sessions = new List<Guid>();
}
public void CommitTransaction(Guid id) {
_sessions.Remove(id);
}
public void DisposeSession(Guid id) {
_sessions.Remove(id);
}
public IClientSessionHandle? GetSession(Guid id) {
return null;
}
public void RollbackTransaction(Guid id) {
_sessions.Remove(id);
}
public Task<Guid> StartSession() {
return Task.Run(() => {
var sessionId = Guid.NewGuid();
_sessions.Add(sessionId);
return sessionId;
});
}
public void StartTransaction(Guid id) {
// do nothing
}
}
}

View File

@ -0,0 +1,48 @@
//using Microsoft.Extensions.Logging;
//using DomainResults.Common;
//using VaultProvider;
//namespace CoreTests.FakeServices {
// public class VaultFakeService : IVaultService {
// private readonly ILogger<VaultFakeService> _logger;
// private Dictionary<string, KeyValuePair<string, object>> _collection;
// public VaultFakeService(
// ILogger<VaultFakeService> logger,
// Dictionary<string, KeyValuePair<string, object>> collection
// ) {
// _logger = logger;
// _collection = collection;
// }
// public (Guid?, IDomainResult) IsTokenWhitelisted(string path, string token) =>
// IsTokenWhitelistedAsync(path, token).Result;
// public Task<(Guid?, IDomainResult)> IsTokenWhitelistedAsync(string path, string token) =>
// Task.Run(() => IDomainResult.Success<Guid?>(Guid.NewGuid()));
// public (string?, IDomainResult) ReadMailboxPassword(string path) =>
// ReadMailboxPasswordAsync(path).Result;
// public Task<(string?, IDomainResult)> ReadMailboxPasswordAsync(string path) =>
// Task.Run(() => {
// KeyValuePair<string, object> kv;
// _collection.TryGetValue(path, out kv);
// if (kv.Value == null)
// return IDomainResult.Failed<string?>();
// return IDomainResult.Success((string)kv.Value);
// });
// public (Guid?, IDomainResult) WriteMailboxPassword(string path, string email, string password) =>
// WriteMailboxPasswordAsync(path, email, password).Result;
// public Task<(Guid?, IDomainResult)> WriteMailboxPasswordAsync(string path, string email, string password) {
// throw new NotImplementedException();
// }
// }
//}

View File

@ -0,0 +1,97 @@
/*
Usage
Add the following attribute to classes for which you want tests run in order:
[TestCaseOrderer(PriorityOrderer.Name, PriorityOrderer.Assembly)]
Then decorate your test methods with the Priority attribute.
[Fact, Priority(-10)]
public void FirstTestToRun() { }
[Fact, Priority(0)]
public void SecondTestToRun() { }
[Fact, Priority(10)]
public void ThirdTestToRunA() { }
[Fact, Priority(10)]
public void ThirdTestToRunB() { }
[Fact]
public void TestsWithNoPriorityRunLast() { }
Priorities are evaluated in numeric order (including 0 and negative numbers). If there are multiple tests with the same priority, those tests will be run in alphabetical order.
By default, tests with no explicit Priority attribute are assigned priority int.MaxValue and will be run last. You can change this by setting a DefaultPriority attribute on your test class.
[DefaultPriority(0)]
public class MyTests
{
[Fact]
public void SomeTest() { }
[Fact]
public void SomeOtherTest() { }
[Fact, Priority(10)]
public void RunMeLast() { }
}
*/
using Xunit.Abstractions;
using Xunit.Sdk;
using Tests.CoreTests.Attributes;
using System.Collections.Concurrent;
namespace CoreTests.Core
{
public class PriorityOrderer : ITestCaseOrderer
{
public const string Name = "Tests.CoreTests.PriorityOrderer";
public const string Assembly = "Tests.CoreTests";
private static string _priorityAttributeName = typeof(PriorityAttribute).AssemblyQualifiedName;
private static string _defaultPriorityAttributeName = typeof(DefaultPriorityAttribute).AssemblyQualifiedName;
private static string _priorityArgumentName = nameof(PriorityAttribute.Priority);
private static ConcurrentDictionary<string, int> _defaultPriorities = new ConcurrentDictionary<string, int>();
public IEnumerable<TTestCase> OrderTestCases<TTestCase>(IEnumerable<TTestCase> testCases) where TTestCase : ITestCase
{
var groupedTestCases = new Dictionary<int, List<ITestCase>>();
var defaultPriorities = new Dictionary<Type, int>();
foreach (var testCase in testCases)
{
var defaultPriority = DefaultPriorityForClass(testCase);
var priority = PriorityForTest(testCase, defaultPriority);
if (!groupedTestCases.ContainsKey(priority))
groupedTestCases[priority] = new List<ITestCase>();
groupedTestCases[priority].Add(testCase);
}
var orderedKeys = groupedTestCases.Keys.OrderBy(k => k);
foreach (var list in orderedKeys.Select(priority => groupedTestCases[priority]))
{
list.Sort((x, y) => StringComparer.OrdinalIgnoreCase.Compare(x.TestMethod.Method.Name, y.TestMethod.Method.Name));
foreach (TTestCase testCase in list)
yield return testCase;
}
}
private int PriorityForTest(ITestCase testCase, int defaultPriority)
{
var priorityAttribute = testCase.TestMethod.Method.GetCustomAttributes(_priorityAttributeName).SingleOrDefault();
return priorityAttribute?.GetNamedArgument<int>(_priorityArgumentName) ?? defaultPriority;
}
private int DefaultPriorityForClass(ITestCase testCase)
{
var testClass = testCase.TestMethod.TestClass.Class;
if (!_defaultPriorities.TryGetValue(testClass.Name, out var result))
{
var defaultAttribute = testClass.GetCustomAttributes(_defaultPriorityAttributeName).SingleOrDefault();
result = defaultAttribute?.GetNamedArgument<int>(_priorityArgumentName) ?? int.MaxValue;
_defaultPriorities[testClass.Name] = result;
}
return result;
}
}
}

View File

@ -0,0 +1,17 @@
using Microsoft.Extensions.DependencyInjection;
using Tests.CoreTests.Abstractions;
namespace ExtensionsTests.Abstractions {
public abstract class ServicesBase : ConfigurationBase {
public ServicesBase() : base() { }
protected override void ConfigureServices(IServiceCollection services) {
// configure strongly typed settings objects
var appSettingsSection = _configuration.GetSection("Configuration");
services.Configure<Configuration>(appSettingsSection);
// var appSettings = appSettingsSection.Get<Configuration>();
}
}
}

View File

@ -0,0 +1,3 @@
namespace ExtensionsTests {
public class Configuration {}
}

View File

@ -0,0 +1,37 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<Nullable>enable</Nullable>
<IsPackable>false</IsPackable>
<RootNamespace>$(MSBuildProjectName.Replace(" ", "_"))</RootNamespace>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="6.0.0" />
<PackageReference Include="Microsoft.Extensions.Options.ConfigurationExtensions" Version="6.0.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.2.0" />
<PackageReference Include="xunit" Version="2.4.1" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.5">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
<PackageReference Include="coverlet.collector" Version="3.1.2">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\Extensions\Extensions.csproj" />
<ProjectReference Include="..\Core\CoreTests.csproj" />
</ItemGroup>
<ItemGroup>
<None Update="appsettings.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
</ItemGroup>
</Project>

View File

@ -0,0 +1,121 @@
using System;
using System.Collections.Generic;
using ExtensionMethods;
using Xunit;
namespace ExtensionsTests {
public class IEquatableExtensionsTests {
private class DummyClass : IEquatable<DummyClass> {
public string? Test { get; set; }
public bool Equals(DummyClass? other) {
if (other == null)
return false;
return other.Test == Test;
}
public override int GetHashCode() {
return Test.GetHashCode();
}
}
[Fact]
public void Eq() {
Assert.True(
!new DummyClass { Test = "test" }.Eq(new DummyClass { Test = "Test" })
&& new DummyClass { Test = "Test" }.Eq(new DummyClass { Test = "Test" })
&& !new DummyClass().Eq(new DummyClass { Test = "Test" })
);
}
[Fact]
public void EnumerableEq() {
var emptyList = new List<DummyClass>();
var list1 = new List<DummyClass> {
new DummyClass {
Test = "12345"
},
new DummyClass {
Test = "67890"
}
};
var list2 = new List<DummyClass> {
new DummyClass {
Test = "67890"
},
new DummyClass {
Test = "12345"
}
};
var list3 = new List<DummyClass> {
new DummyClass {
Test = "12345"
},
new DummyClass {
Test = "ABCDE"
}
};
var list4 = new List<DummyClass> {
new DummyClass {
Test = "12345"
},
new DummyClass {
Test = "67890"
},
new DummyClass {
Test = "67890"
}
};
var list5 = new List<DummyClass> {
new DummyClass {
Test = "12345"
},
new DummyClass {
Test = "12345"
},
new DummyClass {
Test = "67890"
}
};
// compare between two empty lists (true)
var result1 = emptyList.EnumerableEq(emptyList);
// compare between empty and full list (false)
var result2 = emptyList.EnumerableEq(list1);
// compare between same but unordered lists (true)
var result3 = list1.EnumerableEq(list2);
// compare beween two different lists of the same lenght (false)
var result4 = list1.EnumerableEq(list3);
// compare between two different lenght lists containing same elements (false)
var result5 = list1.EnumerableEq(list4);
// compare between different lists with same lenght and same elements but different proportions (false)
var result6 = list4.EnumerableEq(list5);
Assert.True(
result1
&& !result2
&& result3
&& !result4
&& !result5
&& !result6
);
}
}
}

View File

@ -0,0 +1,22 @@
using ExtensionMethods;
using Xunit;
namespace ExtensionsTests {
public class ObjectExtensions {
private class DummyClass {
public string Test { get; set; }
}
[Fact]
public void ToJson() {
var json = new DummyClass {
Test = "Test"
}.ToJson();
Assert.Equal("{\"test\":\"Test\"}", json);
}
}
}

View File

@ -0,0 +1,256 @@
using System;
using System.Text;
using System.Linq;
using System.ComponentModel.DataAnnotations;
using System.Collections.Generic;
using Microsoft.Extensions.Options;
using Microsoft.Extensions.DependencyInjection;
using Xunit;
using ExtensionMethods;
using ExtensionsTests.Abstractions;
using System.IO;
using System.Threading.Tasks;
namespace ExtensionsTests {
public class StringExtensions : ServicesBase {
private readonly Configuration _configuration;
public StringExtensions() {
_configuration = ServiceProvider.GetService<IOptions<Configuration>>()?.Value
?? throw new NullReferenceException("Configuration not found");
}
[Fact]
public void Like() => "http://maks-it.com".Like("https:?//maks*.com");
[Fact]
public void Left() => string.Equals("https://maks-it.com".Left(4), "http");
[Fact]
public void Right() => string.Equals("https://maks-it.com".Right(4), ".com");
[Fact]
public void Mid() => string.Equals("https://maks-it.com".Mid(8, 7), "maks-it");
[Fact]
public void ToInteger() {
var value = "0".ToInteger();
Assert.True(value.GetType() == typeof(int) && value == 0);
}
[Fact]
public void IsInteger() {
Assert.True("123".IsInteger() && !"abc123".IsInteger());
}
[Fact]
public void Prepend() {
var sb = new StringBuilder("123").Prepend("abc");
Assert.Equal("abc123", sb.ToString());
}
private enum DummyEnum {
[Display(Name = "First member")]
Member1,
[Display(Name = "Second member")]
Member2
}
[Fact]
public void ToEnum() {
Assert.True(DummyEnum.Member1 == "First member".ToEnum<DummyEnum>() && DummyEnum.Member2 == "Member2".ToEnum<DummyEnum>());
}
[Fact]
public void ToNullableEnum() => Assert.True(null == "".ToNullableEnum<DummyEnum>() || DummyEnum.Member1 == "Member1".ToNullableEnum<DummyEnum>());
[Fact]
public void ToNull() => Assert.True(null == "".ToNull() && string.Equals("test", "test".ToNull()));
[Fact]
public void ToLong() {
var value = "0".ToLong();
Assert.True(value.GetType() == typeof(long) && value == 0);
}
[Fact]
public void ToNullableLong() {
var value = "0".ToNullableLong();
Assert.True(value == 0 && value.GetType() == typeof(long) && "".ToNullableLong() == null);
}
[Fact]
public void ToInt() {
var value = "0".ToInt();
Assert.True(value.GetType() == typeof(int) && value == 0);
}
[Fact]
public void ToNullableInt() {
var value = "0".ToNullableInt();
Assert.True(value.GetType() == typeof(int) && value == 0 && "".ToNullableInt() == null);
}
[Fact]
public void ToUint() {
var value = "0".ToUint();
Assert.True(value.GetType() == typeof(uint) && value == 0);
}
[Fact]
public void ToNullableUint() {
var value = "0".ToNullableUint();
Assert.True(value.GetType() == typeof(uint) && value == 0 && "".ToNullableUint() == null);
}
[Fact]
public void ToDecimal() {
var value = "0".ToDecimal();
Assert.True(value.GetType() == typeof(decimal) && value == 0);
}
[Fact]
public void ToNullableDecimal() {
var value = "0".ToNullableDecimal();
Assert.True(value.GetType() == typeof(decimal) && "".ToNullableDecimal() == null);
}
[Fact]
public void ToDouble() {
var value = "0".ToDouble();
Assert.True(value.GetType() == typeof(double) && value == 0);
}
[Fact]
public void ToNullableDouble() {
var value = "0".ToNullableDouble();
Assert.True(value.GetType() == typeof(double) && value == 0 && "".ToNullableDouble() == null);
}
[Fact]
public void ToDate() {
var value = "05/08/1988".ToDate();
Assert.True(value.GetType() == typeof(DateTime) && value == new DateTime(1988, 08, 05));
}
[Fact]
public void ToNullableDate() {
var value = "05/08/1988".ToDate();
Assert.True(value.GetType() == typeof(DateTime) && value == new DateTime(1988, 08, 05) && "".ToNullableDate() == null);
}
[Fact]
public void ToDateTime() {
var value = "05/08/1988 00:30:00".ToDateTime();
Assert.True(value.GetType() == typeof(DateTime) && value == new DateTime(1988, 08, 05, 00, 30, 00));
}
[Fact]
public void ToNullableDateTime() {
var value = "05/08/1988 00:30:00".ToDateTime();
Assert.True(value.GetType() == typeof(DateTime) && value == new DateTime(1988, 08, 05, 00, 30, 00) && "".ToNullableDateTime() == null);
}
[Fact]
public void ToBool() {
Assert.True("true".ToBool() && !"false".ToBool());
}
[Fact]
public void ToNullalbeBool() {
Assert.True("true".ToNullableBool().Value && !"false".ToNullableBool().Value && "".ToNullableBool() == null);
}
[Fact]
public void ToGuid() {
var value = "7a11f6ed-d1b6-4d38-9f66-738762c4fde6".ToGuid();
Assert.True(value.GetType() == typeof(Guid));
}
[Fact]
public void ToNullableGuid() => Assert.Null("".ToNullableGuid());
[Fact]
public void StringSplit() => Assert.True(new string[] { "hello", "world" }.SequenceEqual("hello, world".StringSplit(',')));
[Fact]
public void ToTitle() => Assert.Equal("Maks-IT", "maks-IT".ToTitle());
[Fact]
public void ExtractUrls() => Assert.True(new string[] { "https://maks-it.com/123", "https://git.maks-it.com/123" }
.SequenceEqual(
"Here are some urls that we have to extract! This one: https://maks-it.com/123 and this one too https://git.maks-it.com/123"
.ExtractUrls().Select(x => x.AbsoluteUri.ToString()
)));
[Fact]
public void Format() => Assert.Equal("MAKS-IT is a consulting company", "{0} is a consulting company".Format("MAKS-IT"));
[Fact]
public void Excerpt() => Assert.Equal("M...", "MAKS-IT".Excerpt(4));
private class DummyClass {
public string Test { get; set; }
}
[Fact]
public void ToObject() {
var obj = "{ \"Test\": \"Test\" }".ToObject<DummyClass>();
Assert.Equal("Test", obj.Test);
}
public static IEnumerable<object[]> GetEmailValidationData() {
var allData = new List<object[]> {
new object [] { "BAR.SPORT.SAS.@LEGALMAIL.IT", false },
new object [] { "DANICONFCOM@PEC..IT", false },
new object [] { "FRANCESCOF..90@PEC.IT", false },
new object [] { "grecoornellas.a.s.@pec.it", false },
new object [] { "lavillamar.@pec.libero.it", false },
new object [] { "podda.pietro.@tiscali.it", false },
new object [] { "scads.r.l.@legalmail.it", false },
new object [] { "ALESSANDRO.DOTTI@ARCHIWORLDPEC.IT", true },
new object [] { "alessandro.durante@mastertrucksrl.it", true },
new object [] { "alessandro.elia@pec.lottomatica.it", true },
new object [] { "ALESSANDRO.EMILIANI@PEC.IT", true },
new object [] { "alessandro.falco@pec.lottomatica.it", true },
new object [] { "ALESSANDRO.FANNI@PEC.IT", true },
new object [] { "ALESSANDRO.FASULO@PEC.IT", true }
};
return allData;
}
[Theory]
[MemberData(nameof(GetEmailValidationData))]
public void IsValidEmail(string email, bool expected) {
var result = email.IsValidEmail();
Assert.Equal(expected, result);
}
[Fact]
public void HTMLToPlainText() {
var html = @"<Grid>
<RichTextBox>
<FlowDocument>
<p name=""p1"">Hello</p>
<p name=""p2"">World!</p>
</FlowDocument>
</RichTextBox>
</Grid>";
var result = html.HTMLToPlainText();
var expected = "Hello \nWorld!";
Assert.True(string.Compare(expected, result) == 0);
}
}
}

View File

@ -0,0 +1,5 @@
{
"Configuration": {
}
}

View File

@ -19,6 +19,14 @@ Project("{E53339B2-1760-4266-BCC7-CA923CBCF16C}") = "docker-compose", "docker-co
EndProject
Project("{9092AA53-FB77-4645-B42D-1CCCA6BD08BD}") = "ClientApp", "ClientApp\ClientApp.njsproj", "{8531F7E2-07D6-4EC5-B3E6-316BBF4499E4}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Extensions", "Extensions\Extensions.csproj", "{94A44D75-4AE2-4F1A-A7E9-821710B19F93}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Tests", "Tests", "{216302C2-64DE-4AE7-BC14-BDAC5B732472}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ExtensionsTests", "Tests\Extensions\ExtensionsTests.csproj", "{43315A1D-9E09-4398-84B9-A9D9D623AE5A}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CoreTests", "Tests\Core\CoreTests.csproj", "{04CB9827-AA6D-4708-A26D-8420C842506D}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@ -53,6 +61,18 @@ Global
{8531F7E2-07D6-4EC5-B3E6-316BBF4499E4}.Debug|Any CPU.Build.0 = Debug|Any CPU
{8531F7E2-07D6-4EC5-B3E6-316BBF4499E4}.Release|Any CPU.ActiveCfg = Release|Any CPU
{8531F7E2-07D6-4EC5-B3E6-316BBF4499E4}.Release|Any CPU.Build.0 = Release|Any CPU
{94A44D75-4AE2-4F1A-A7E9-821710B19F93}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{94A44D75-4AE2-4F1A-A7E9-821710B19F93}.Debug|Any CPU.Build.0 = Debug|Any CPU
{94A44D75-4AE2-4F1A-A7E9-821710B19F93}.Release|Any CPU.ActiveCfg = Release|Any CPU
{94A44D75-4AE2-4F1A-A7E9-821710B19F93}.Release|Any CPU.Build.0 = Release|Any CPU
{43315A1D-9E09-4398-84B9-A9D9D623AE5A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{43315A1D-9E09-4398-84B9-A9D9D623AE5A}.Debug|Any CPU.Build.0 = Debug|Any CPU
{43315A1D-9E09-4398-84B9-A9D9D623AE5A}.Release|Any CPU.ActiveCfg = Release|Any CPU
{43315A1D-9E09-4398-84B9-A9D9D623AE5A}.Release|Any CPU.Build.0 = Release|Any CPU
{04CB9827-AA6D-4708-A26D-8420C842506D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{04CB9827-AA6D-4708-A26D-8420C842506D}.Debug|Any CPU.Build.0 = Debug|Any CPU
{04CB9827-AA6D-4708-A26D-8420C842506D}.Release|Any CPU.ActiveCfg = Release|Any CPU
{04CB9827-AA6D-4708-A26D-8420C842506D}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@ -60,6 +80,8 @@ Global
GlobalSection(NestedProjects) = preSolution
{B8F84A37-B54B-4606-9BC3-6FEB96A5A34B} = {113EE574-E047-4727-AA36-841F845504D5}
{B717D8BD-BCCA-4515-9A62-CA3BE802D0F7} = {113EE574-E047-4727-AA36-841F845504D5}
{43315A1D-9E09-4398-84B9-A9D9D623AE5A} = {216302C2-64DE-4AE7-BC14-BDAC5B732472}
{04CB9827-AA6D-4708-A26D-8420C842506D} = {216302C2-64DE-4AE7-BC14-BDAC5B732472}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {E2805D02-2425-424C-921D-D97341B76F73}