diff --git a/AMWD.Common.Tests/Extensions/CryptographyHelperExtensionsTests.cs b/AMWD.Common.Tests/Extensions/CryptographyHelperExtensionsTests.cs index 0863776..4852e6a 100644 --- a/AMWD.Common.Tests/Extensions/CryptographyHelperExtensionsTests.cs +++ b/AMWD.Common.Tests/Extensions/CryptographyHelperExtensionsTests.cs @@ -4,6 +4,7 @@ using Microsoft.VisualStudio.TestTools.UnitTesting; namespace AMWD.Common.Tests.Extensions { [TestClass] + [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public class CryptographyHelperExtensionsTests { [TestMethod] diff --git a/AMWD.Common.Tests/Extensions/DateTimeExtensionsTests.cs b/AMWD.Common.Tests/Extensions/DateTimeExtensionsTests.cs index ad9878a..29531df 100644 --- a/AMWD.Common.Tests/Extensions/DateTimeExtensionsTests.cs +++ b/AMWD.Common.Tests/Extensions/DateTimeExtensionsTests.cs @@ -5,6 +5,7 @@ using Microsoft.VisualStudio.TestTools.UnitTesting; namespace AMWD.Common.Tests.Extensions { [TestClass] + [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public class DateTimeExtensionsTests { [TestMethod] diff --git a/AMWD.Common.Tests/Extensions/EnumExtensionsTests.cs b/AMWD.Common.Tests/Extensions/EnumExtensionsTests.cs index 62f91bc..0689300 100644 --- a/AMWD.Common.Tests/Extensions/EnumExtensionsTests.cs +++ b/AMWD.Common.Tests/Extensions/EnumExtensionsTests.cs @@ -7,6 +7,7 @@ using DescriptionAttribute = System.ComponentModel.DescriptionAttribute; namespace AMWD.Common.Tests.Extensions { [TestClass] + [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public class EnumExtensionsTests { [TestMethod] @@ -93,6 +94,19 @@ namespace AMWD.Common.Tests.Extensions Assert.AreEqual(enumWithoutDescripton.ToString(), noDescription); } + [TestMethod] + public void ShouldReturnEmptyListOnNotDefinedEnumValue() + { + // arrange + var notDefinedEnum = (TestEnum)10; + + // act + var list = notDefinedEnum.GetAttributes(); + + // assert + Assert.IsFalse(list.Any()); + } + internal enum TestEnum { [CustomMultiple("nix")] diff --git a/AMWD.Common.Tests/Extensions/ExceptionExtensionsTests.cs b/AMWD.Common.Tests/Extensions/ExceptionExtensionsTests.cs new file mode 100644 index 0000000..8e691dc --- /dev/null +++ b/AMWD.Common.Tests/Extensions/ExceptionExtensionsTests.cs @@ -0,0 +1,75 @@ +using System; +using System.Collections.Generic; +using Microsoft.VisualStudio.TestTools.UnitTesting; + +namespace AMWD.Common.Tests.Extensions +{ + [TestClass] + [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] + public class ExceptionExtensionsTests + { + [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 + { + new Exception("Inner Exception 1."), + new Exception("Inner Exception 2. See the inner exception for details.", new Exception("Inner Exception of Exception 2.")), + new Exception("Inner Exception 3."), + new Exception("Inner Exception 4."), + new Exception("Inner Exception 5.") + }; + 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); + } + } +} diff --git a/AMWD.Common.Tests/Extensions/JsonExtensionsTests.cs b/AMWD.Common.Tests/Extensions/JsonExtensionsTests.cs index ca36604..ef2af2f 100644 --- a/AMWD.Common.Tests/Extensions/JsonExtensionsTests.cs +++ b/AMWD.Common.Tests/Extensions/JsonExtensionsTests.cs @@ -1,4 +1,5 @@ using System; +using System.Collections; using AMWD.Common.Tests.Utils; using Microsoft.VisualStudio.TestTools.UnitTesting; using Newtonsoft.Json; @@ -7,6 +8,7 @@ using Newtonsoft.Json.Linq; namespace AMWD.Common.Tests.Extensions { [TestClass] + [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public class JsonExtensionsTests { [TestMethod] @@ -303,5 +305,37 @@ namespace AMWD.Common.Tests.Extensions 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("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); + } } } diff --git a/AMWD.Common.Tests/Extensions/ReaderWriterLockSlimExtensionsTests.cs b/AMWD.Common.Tests/Extensions/ReaderWriterLockSlimExtensionsTests.cs index ca839ce..12bea95 100644 --- a/AMWD.Common.Tests/Extensions/ReaderWriterLockSlimExtensionsTests.cs +++ b/AMWD.Common.Tests/Extensions/ReaderWriterLockSlimExtensionsTests.cs @@ -1,9 +1,12 @@ +using System; using System.Threading; +using System.Threading.Tasks; using Microsoft.VisualStudio.TestTools.UnitTesting; namespace AMWD.Common.Tests.Extensions { [TestClass] + [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public class ReaderWriterLockSlimExtensionsTests { [TestMethod] @@ -38,6 +41,39 @@ namespace AMWD.Common.Tests.Extensions 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() { @@ -85,14 +121,101 @@ namespace AMWD.Common.Tests.Extensions // act using var disposableRead = rwLock.GetUpgradeableReadLock(); - using var disposaleWrite = rwLock.GetWriteLock(); + using var disposableWrite = rwLock.GetWriteLock(); // assert Assert.IsNotNull(disposableRead); - Assert.IsNotNull(disposaleWrite); + 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 */ } + }); + Task.WaitAll(awaitableTask); + + // 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 */ } + }); + Task.WaitAll(awaitableTask); + + // 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 */ } + }); + Task.WaitAll(awaitableTask); + + // assert + Assert.IsTrue(isTimeout); + } } } diff --git a/AMWD.Common.Tests/Extensions/StringExtensionsTests.cs b/AMWD.Common.Tests/Extensions/StringExtensionsTests.cs index 0770ef2..162290c 100644 --- a/AMWD.Common.Tests/Extensions/StringExtensionsTests.cs +++ b/AMWD.Common.Tests/Extensions/StringExtensionsTests.cs @@ -7,6 +7,7 @@ using Microsoft.VisualStudio.TestTools.UnitTesting; namespace AMWD.Common.Tests.Extensions { [TestClass] + [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public class StringExtensionsTests { [TestMethod] @@ -132,10 +133,12 @@ namespace AMWD.Common.Tests.Extensions string plain = "Hello"; // act - string hex = plain.HexEncode(Encoding.UTF8); + string hex1 = plain.HexEncode(); + string hex2 = plain.HexEncode(Encoding.Default); // assert - Assert.AreEqual("48656c6c6f", hex); + Assert.AreEqual("48656c6c6f", hex1); + Assert.AreEqual("48656c6c6f", hex2); } [TestMethod] @@ -145,10 +148,12 @@ namespace AMWD.Common.Tests.Extensions string hex = "48656c6c6f"; // act - string plain = hex.HexDecode(Encoding.UTF8); + string plain1 = hex.HexDecode(); + string plain2 = hex.HexDecode(Encoding.Default); // assert - Assert.AreEqual("Hello", plain); + Assert.AreEqual("Hello", plain1); + Assert.AreEqual("Hello", plain2); } [TestMethod] @@ -158,10 +163,12 @@ namespace AMWD.Common.Tests.Extensions string plain = "Hello"; // act - string base64 = plain.Base64Encode(Encoding.UTF8); + string base641 = plain.Base64Encode(); + string base642 = plain.Base64Encode(Encoding.Default); // assert - Assert.AreEqual("SGVsbG8=", base64); + Assert.AreEqual("SGVsbG8=", base641); + Assert.AreEqual("SGVsbG8=", base642); } [TestMethod] @@ -171,10 +178,12 @@ namespace AMWD.Common.Tests.Extensions string base64 = "SGVsbG8="; // act - string plain = base64.Base64Decode(Encoding.UTF8); + string plain1 = base64.Base64Decode(); + string plain2 = base64.Base64Decode(Encoding.Default); // assert - Assert.AreEqual("Hello", plain); + Assert.AreEqual("Hello", plain1); + Assert.AreEqual("Hello", plain2); } [TestMethod] @@ -255,18 +264,21 @@ namespace AMWD.Common.Tests.Extensions string validEmailWithTag = "test+tag@not.exists"; string invalidEmailWithoutTag = " test@gmail.com"; string invalidEmailWithTag = " test+tag@not.exists"; + string nullStr = null; // act bool validWithoutTag = validEmailWithoutTag.IsValidEmailAddress(checkRecordExists: false); bool validWithTag = validEmailWithTag.IsValidEmailAddress(checkRecordExists: false); bool invalidWithoutTag = !invalidEmailWithoutTag.IsValidEmailAddress(checkRecordExists: false); bool invalidWithTag = !invalidEmailWithTag.IsValidEmailAddress(checkRecordExists: false); + bool nullTest = nullStr.IsValidEmailAddress(checkRecordExists: false); // assert Assert.IsTrue(validWithoutTag); Assert.IsTrue(validWithTag); Assert.IsTrue(invalidWithoutTag); Assert.IsTrue(invalidWithTag); + Assert.IsFalse(nullTest); } [TestMethod] @@ -301,5 +313,25 @@ namespace AMWD.Common.Tests.Extensions 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); + } } } diff --git a/AMWD.Common.Tests/Utilities/CryptographyHelperTests.cs b/AMWD.Common.Tests/Utilities/CryptographyHelperTests.cs index 347cdb3..a702648 100644 --- a/AMWD.Common.Tests/Utilities/CryptographyHelperTests.cs +++ b/AMWD.Common.Tests/Utilities/CryptographyHelperTests.cs @@ -1,6 +1,7 @@ using System; using System.Diagnostics; using System.IO; +using System.Reflection; using System.Security.Cryptography; using System.Text.RegularExpressions; using AMWD.Common.Tests.Utils; @@ -9,6 +10,7 @@ using Microsoft.VisualStudio.TestTools.UnitTesting; namespace AMWD.Common.Tests.Utilities { [TestClass] + [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public class CryptographyHelperTests { private string keyFile; @@ -430,10 +432,15 @@ namespace AMWD.Common.Tests.Utilities 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; @@ -446,10 +453,12 @@ namespace AMWD.Common.Tests.Utilities sw.Stop(); // assert + Assert.IsFalse(nullCompare); + Assert.IsFalse(lenCompare); Assert.IsTrue(compare1); Assert.IsFalse(compare2); Assert.IsFalse(compare3); - // max. time delta: 2 ticks per character + Assert.AreEqual(time1, time2); Assert.AreEqual(time1, time3); Assert.AreEqual(time2, time3); @@ -463,6 +472,8 @@ namespace AMWD.Common.Tests.Utilities 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); @@ -474,6 +485,9 @@ namespace AMWD.Common.Tests.Utilities 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; @@ -486,10 +500,12 @@ namespace AMWD.Common.Tests.Utilities sw.Stop(); // assert + Assert.IsFalse(nullCompare); + Assert.IsFalse(lenCompare); Assert.IsTrue(compare1); Assert.IsFalse(compare2); Assert.IsFalse(compare3); - // max. time delta: 2 ticks per byte + Assert.AreEqual(time1, time2); Assert.AreEqual(time1, time3); Assert.AreEqual(time2, time3); @@ -501,6 +517,28 @@ namespace AMWD.Common.Tests.Utilities #region Instance + [TestMethod] + public void ShouldCreateDefaultFile() + { + // arrange + string executingAssemblyDir = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location); + 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() { diff --git a/AMWD.Common.Tests/Utilities/DelayedTaskTests.cs b/AMWD.Common.Tests/Utilities/DelayedTaskTests.cs index ad61841..9cdce47 100644 --- a/AMWD.Common.Tests/Utilities/DelayedTaskTests.cs +++ b/AMWD.Common.Tests/Utilities/DelayedTaskTests.cs @@ -8,6 +8,7 @@ using Microsoft.VisualStudio.TestTools.UnitTesting; namespace AMWD.Common.Tests.Utilities { [TestClass] + [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public class DelayedTaskTests { // TODO diff --git a/AMWD.Common.Tests/Utils/JsonTestClass.cs b/AMWD.Common.Tests/Utils/JsonTestClass.cs index 9d1ed3b..2bf41d2 100644 --- a/AMWD.Common.Tests/Utils/JsonTestClass.cs +++ b/AMWD.Common.Tests/Utils/JsonTestClass.cs @@ -29,4 +29,22 @@ namespace AMWD.Common.Tests.Utils 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; } + } + } } diff --git a/AMWD.Common/Extensions/ExceptionExtensions.cs b/AMWD.Common/Extensions/ExceptionExtensions.cs index bece785..d1a0aa3 100644 --- a/AMWD.Common/Extensions/ExceptionExtensions.cs +++ b/AMWD.Common/Extensions/ExceptionExtensions.cs @@ -5,7 +5,6 @@ namespace System /// /// Provides extension methods for exceptions. /// - [Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public static class ExceptionExtensions { /// diff --git a/AMWD.Common/Extensions/ReaderWriterLockSlimExtensions.cs b/AMWD.Common/Extensions/ReaderWriterLockSlimExtensions.cs index 40de6cb..6af910e 100644 --- a/AMWD.Common/Extensions/ReaderWriterLockSlimExtensions.cs +++ b/AMWD.Common/Extensions/ReaderWriterLockSlimExtensions.cs @@ -1,6 +1,4 @@ -//[assembly: System.Runtime.CompilerServices.InternalsVisibleTo("AMWD.Common.Tests")] - -namespace System.Threading +namespace System.Threading { /// /// Provides extension methods for the .