From 19bb8ed3200d92027cb07c5416ef8e347f4abe99 Mon Sep 17 00:00:00 2001 From: Matthew Layton <9935122+MrMatthewLayton@users.noreply.github.com> Date: Sun, 23 Jun 2024 09:56:12 +0100 Subject: [PATCH] feature/type-conversions (#71) * Adding several features including async extensions for working with the `Result` and `Result` types. This also includes several bug fixes and improves test coverage. * Added more unit tests, xmldoc and general code cleanup. Version bumped to 8.14.0 * Removed playground code. --- OnixLabs.Core.UnitTests.Data/Disposable.cs | 31 + .../InvalidFormatProvider.cs | 26 + .../OnixLabs.Core.UnitTests.Data.csproj | 8 +- OnixLabs.Core.UnitTests/EnumerationTests.cs | 18 + .../Linq/IEnumerableExtensionTests.cs | 70 ++ .../ObjectExtensionTests.cs | 62 ++ .../OnixLabs.Core.UnitTests.csproj | 8 +- OnixLabs.Core.UnitTests/OptionalTests.cs | 39 +- OnixLabs.Core.UnitTests/PreconditionTests.cs | 123 +++ .../ResultEqualityComparerTests.cs | 15 + .../ResultExtensionTests.cs | 668 +++++++++++++++- OnixLabs.Core.UnitTests/ResultGenericTests.cs | 743 ++++++++++++++++++ .../ResultNonGenericTests.cs | 371 --------- OnixLabs.Core.UnitTests/ResultTests.cs | 397 ++++------ .../StringExtensionTests.cs | 93 ++- .../Text/Base16CodecInvariantTests.cs | 58 -- .../Text/Base16CodecLowercaseTests.cs | 58 -- .../Text/Base16CodecTests.cs | 218 +++++ .../Text/Base16CodecUppercaseTests.cs | 58 -- .../Text/Base16FormatProviderTests.cs | 106 +++ OnixLabs.Core.UnitTests/Text/Base16Tests.cs | 190 ++++- .../Text/Base32CodecBase32HexTests.cs | 58 -- .../Text/Base32CodecCrockfordTests.cs | 58 -- .../Text/Base32CodecGeoHashTests.cs | 58 -- .../Text/Base32CodecPaddedBase32HexTests.cs | 58 -- .../Text/Base32CodecPaddedCrockfordTests.cs | 58 -- .../Text/Base32CodecPaddedGeoHashTests.cs | 58 -- .../Text/Base32CodecPaddedRfc4648Tests.cs | 58 -- .../Text/Base32CodecPaddedZBase32Tests.cs | 58 -- .../Text/Base32CodecRfc4648Tests.cs | 58 -- .../Text/Base32CodecTests.cs | 494 ++++++++++++ .../Text/Base32CodecZBase32Tests.cs | 58 -- .../Text/Base32FormatProviderTests.cs | 269 +++++++ OnixLabs.Core.UnitTests/Text/Base32Tests.cs | 190 ++++- .../Text/Base58CodecBitcoinTests.cs | 58 -- .../Text/Base58CodecFlickrTests.cs | 58 -- .../Text/Base58CodecRippleTests.cs | 58 -- .../Text/Base58CodecTests.cs | 204 +++++ .../Text/Base58FormatProviderTests.cs | 106 +++ OnixLabs.Core.UnitTests/Text/Base58Tests.cs | 190 ++++- .../Text/Base64CodecTests.cs | 44 ++ .../Text/Base64FormatProviderTests.cs | 68 ++ OnixLabs.Core.UnitTests/Text/Base64Tests.cs | 178 +++++ .../Text/ExtensionTests.cs | 166 ++++ .../Text/IBaseCodecTests.cs | 210 +++++ OnixLabs.Core/Extensions.Object.cs | 2 +- OnixLabs.Core/Extensions.Result.cs | 238 ++++++ OnixLabs.Core/Extensions.String.cs | 64 +- OnixLabs.Core/Linq/Extensions.IEnumerable.cs | 11 +- OnixLabs.Core/OnixLabs.Core.csproj | 4 +- OnixLabs.Core/Optional.cs | 2 +- OnixLabs.Core/Result.Failure.cs | 41 +- OnixLabs.Core/Result.Success.cs | 40 +- OnixLabs.Core/Result.cs | 16 +- OnixLabs.Core/Text/Base16.Convertible.cs | 60 ++ OnixLabs.Core/Text/Base16Codec.cs | 8 +- OnixLabs.Core/Text/Base32.Convertible.cs | 60 ++ OnixLabs.Core/Text/Base32Codec.cs | 9 +- OnixLabs.Core/Text/Base58.Convertible.cs | 60 ++ OnixLabs.Core/Text/Base58Codec.cs | 14 +- OnixLabs.Core/Text/Base64.Convertible.cs | 60 ++ OnixLabs.Core/Text/Base64Codec.cs | 24 + OnixLabs.Core/Text/Extensions.cs | 2 +- OnixLabs.Core/Text/IBaseCodec.cs | 30 +- .../OnixLabs.Numerics.UnitTests.Data.csproj | 8 +- .../OnixLabs.Numerics.UnitTests.csproj | 8 +- OnixLabs.Numerics/OnixLabs.Numerics.csproj | 4 +- ...ecurity.Cryptography.UnitTests.Data.csproj | 8 +- .../HashAlgorithmExtensionTests.cs | 3 +- .../HashTests.cs | 5 +- .../MerkleTreeGenericTests.cs | 6 + .../MerkleTreeTests.cs | 6 + ...abs.Security.Cryptography.UnitTests.csproj | 8 +- .../DigitalSignature.Convertible.cs | 34 + .../EcdhPrivateKey.Convertible.cs | 34 + .../EcdhPrivateKey.Import.cs | 43 +- .../EcdhPublicKey.Convertible.cs | 34 + .../EcdsaPrivateKey.Convertible.cs | 34 + .../EcdsaPrivateKey.Import.cs | 43 +- .../EcdsaPublicKey.Convertible.cs | 34 + .../Extensions.HashAlgorithm.cs | 21 + OnixLabs.Security.Cryptography/Extensions.cs | 54 ++ .../Hash.Compute.cs | 85 +- .../Hash.Convertible.cs | 34 + .../IPrivateKeyImportablePkcs8.cs | 33 + .../OnixLabs.Security.Cryptography.csproj | 4 +- .../PrivateKey.To.cs | 7 + .../PublicKey.To.cs | 7 + .../RsaPrivateKey.Convertible.cs | 34 + .../RsaPrivateKey.Import.cs | 43 +- .../RsaPublicKey.Convertible.cs | 34 + .../Secret.Convertible.cs | 17 +- .../OnixLabs.Security.UnitTests.csproj | 14 +- .../SecurityTokenBuilderTests.cs | 42 + .../SecurityTokenTests.cs | 17 + OnixLabs.Security/OnixLabs.Security.csproj | 4 +- 96 files changed, 5954 insertions(+), 1786 deletions(-) create mode 100644 OnixLabs.Core.UnitTests.Data/Disposable.cs create mode 100644 OnixLabs.Core.UnitTests.Data/InvalidFormatProvider.cs create mode 100644 OnixLabs.Core.UnitTests/ResultGenericTests.cs delete mode 100644 OnixLabs.Core.UnitTests/ResultNonGenericTests.cs delete mode 100644 OnixLabs.Core.UnitTests/Text/Base16CodecInvariantTests.cs delete mode 100644 OnixLabs.Core.UnitTests/Text/Base16CodecLowercaseTests.cs create mode 100644 OnixLabs.Core.UnitTests/Text/Base16CodecTests.cs delete mode 100644 OnixLabs.Core.UnitTests/Text/Base16CodecUppercaseTests.cs create mode 100644 OnixLabs.Core.UnitTests/Text/Base16FormatProviderTests.cs delete mode 100644 OnixLabs.Core.UnitTests/Text/Base32CodecBase32HexTests.cs delete mode 100644 OnixLabs.Core.UnitTests/Text/Base32CodecCrockfordTests.cs delete mode 100644 OnixLabs.Core.UnitTests/Text/Base32CodecGeoHashTests.cs delete mode 100644 OnixLabs.Core.UnitTests/Text/Base32CodecPaddedBase32HexTests.cs delete mode 100644 OnixLabs.Core.UnitTests/Text/Base32CodecPaddedCrockfordTests.cs delete mode 100644 OnixLabs.Core.UnitTests/Text/Base32CodecPaddedGeoHashTests.cs delete mode 100644 OnixLabs.Core.UnitTests/Text/Base32CodecPaddedRfc4648Tests.cs delete mode 100644 OnixLabs.Core.UnitTests/Text/Base32CodecPaddedZBase32Tests.cs delete mode 100644 OnixLabs.Core.UnitTests/Text/Base32CodecRfc4648Tests.cs create mode 100644 OnixLabs.Core.UnitTests/Text/Base32CodecTests.cs delete mode 100644 OnixLabs.Core.UnitTests/Text/Base32CodecZBase32Tests.cs create mode 100644 OnixLabs.Core.UnitTests/Text/Base32FormatProviderTests.cs delete mode 100644 OnixLabs.Core.UnitTests/Text/Base58CodecBitcoinTests.cs delete mode 100644 OnixLabs.Core.UnitTests/Text/Base58CodecFlickrTests.cs delete mode 100644 OnixLabs.Core.UnitTests/Text/Base58CodecRippleTests.cs create mode 100644 OnixLabs.Core.UnitTests/Text/Base58CodecTests.cs create mode 100644 OnixLabs.Core.UnitTests/Text/Base58FormatProviderTests.cs create mode 100644 OnixLabs.Core.UnitTests/Text/Base64FormatProviderTests.cs create mode 100644 OnixLabs.Core.UnitTests/Text/ExtensionTests.cs create mode 100644 OnixLabs.Core.UnitTests/Text/IBaseCodecTests.cs create mode 100644 OnixLabs.Core/Text/Base16.Convertible.cs create mode 100644 OnixLabs.Core/Text/Base32.Convertible.cs create mode 100644 OnixLabs.Core/Text/Base58.Convertible.cs create mode 100644 OnixLabs.Core/Text/Base64.Convertible.cs create mode 100644 OnixLabs.Security.Cryptography/DigitalSignature.Convertible.cs create mode 100644 OnixLabs.Security.Cryptography/EcdhPrivateKey.Convertible.cs create mode 100644 OnixLabs.Security.Cryptography/EcdhPublicKey.Convertible.cs create mode 100644 OnixLabs.Security.Cryptography/EcdsaPrivateKey.Convertible.cs create mode 100644 OnixLabs.Security.Cryptography/EcdsaPublicKey.Convertible.cs create mode 100644 OnixLabs.Security.Cryptography/Extensions.cs create mode 100644 OnixLabs.Security.Cryptography/Hash.Convertible.cs create mode 100644 OnixLabs.Security.Cryptography/RsaPrivateKey.Convertible.cs create mode 100644 OnixLabs.Security.Cryptography/RsaPublicKey.Convertible.cs diff --git a/OnixLabs.Core.UnitTests.Data/Disposable.cs b/OnixLabs.Core.UnitTests.Data/Disposable.cs new file mode 100644 index 0000000..9ec964b --- /dev/null +++ b/OnixLabs.Core.UnitTests.Data/Disposable.cs @@ -0,0 +1,31 @@ +// Copyright 2020 ONIXLabs +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +namespace OnixLabs.Core.UnitTests.Data; + +public sealed class Disposable : IDisposable, IAsyncDisposable +{ + public bool IsDisposed { get; private set; } + + public void Dispose() + { + IsDisposed = true; + } + + public async ValueTask DisposeAsync() + { + IsDisposed = true; + await ValueTask.CompletedTask; + } +} diff --git a/OnixLabs.Core.UnitTests.Data/InvalidFormatProvider.cs b/OnixLabs.Core.UnitTests.Data/InvalidFormatProvider.cs new file mode 100644 index 0000000..494541d --- /dev/null +++ b/OnixLabs.Core.UnitTests.Data/InvalidFormatProvider.cs @@ -0,0 +1,26 @@ +// Copyright 2020 ONIXLabs +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +namespace OnixLabs.Core.UnitTests.Data; + +public sealed class InvalidFormatProvider : IFormatProvider +{ + public static readonly InvalidFormatProvider Instance = new(); + + private InvalidFormatProvider() + { + } + + public object? GetFormat(Type? formatType) => null; +} diff --git a/OnixLabs.Core.UnitTests.Data/OnixLabs.Core.UnitTests.Data.csproj b/OnixLabs.Core.UnitTests.Data/OnixLabs.Core.UnitTests.Data.csproj index 470e521..3bb46b3 100644 --- a/OnixLabs.Core.UnitTests.Data/OnixLabs.Core.UnitTests.Data.csproj +++ b/OnixLabs.Core.UnitTests.Data/OnixLabs.Core.UnitTests.Data.csproj @@ -8,13 +8,13 @@ false - - - + + + runtime; build; native; contentfiles; analyzers; buildtransitive all - + runtime; build; native; contentfiles; analyzers; buildtransitive all diff --git a/OnixLabs.Core.UnitTests/EnumerationTests.cs b/OnixLabs.Core.UnitTests/EnumerationTests.cs index 5ed15a8..a09f1c9 100644 --- a/OnixLabs.Core.UnitTests/EnumerationTests.cs +++ b/OnixLabs.Core.UnitTests/EnumerationTests.cs @@ -29,6 +29,8 @@ public void EnumerationsShouldBeEqual() // Then Assert.Equal(a, b); + Assert.True(a == b); + Assert.False(a != b); } [Fact(DisplayName = "Enumerations should not be equal")] @@ -40,6 +42,8 @@ public void EnumerationsShouldNotBeEqual() // Then Assert.NotEqual(a, b); + Assert.True(a != b); + Assert.False(a == b); } [Fact(DisplayName = "Enumeration should return all enumeration instances")] @@ -149,4 +153,18 @@ public void EnumerationCompareToAsObjectShouldReturnTheCorrectValue() // Then Assert.Equal(-1, actual); } + + [Fact(DisplayName = "Enumeration.ToString should produce the expected result")] + public void EnumerationToStringShouldProduceExpectedResult() + { + // Given + const string expected = "Red"; + Color red = Color.Red; + + // When + string actual = red.ToString(); + + // Then + Assert.Equal(expected, actual); + } } diff --git a/OnixLabs.Core.UnitTests/Linq/IEnumerableExtensionTests.cs b/OnixLabs.Core.UnitTests/Linq/IEnumerableExtensionTests.cs index 41e27ea..d5cf15a 100644 --- a/OnixLabs.Core.UnitTests/Linq/IEnumerableExtensionTests.cs +++ b/OnixLabs.Core.UnitTests/Linq/IEnumerableExtensionTests.cs @@ -480,6 +480,76 @@ public void NoneShouldProduceExpectedResultFalseAll() Assert.False(result); } + [Fact(DisplayName = "IEnumerable.SequenceEqualOrNull should return true when the current and other enumerables are null.")] + public void SequenceEqualOrNullShouldReturnTrueWhenCurrentAndOtherEnumerablesAreNull() + { + // Given + IEnumerable? enumerable = null; + IEnumerable? other = null; + + // When + bool result = enumerable.SequenceEqualOrNull(other); + + // Then + Assert.True(result); + } + + [Fact(DisplayName = "IEnumerable.SequenceEqualOrNull should return false when the current enumerable is not null and other enumerable is null.")] + public void SequenceEqualOrNullShouldReturnTrueWhenCurrentEnumerableIsNotNullAndOtherEnumerableIsNull() + { + // Given + IEnumerable enumerable = [1, 2, 3]; + IEnumerable? other = null; + + // When + bool result = enumerable.SequenceEqualOrNull(other); + + // Then + Assert.False(result); + } + + [Fact(DisplayName = "IEnumerable.SequenceEqualOrNull should return false when the current enumerable is null and other enumerable is not null.")] + public void SequenceEqualOrNullShouldReturnTrueWhenCurrentEnumerableIsNullAndOtherEnumerableIsNotNull() + { + // Given + IEnumerable? enumerable = null; + IEnumerable other = [1, 2, 3]; + + // When + bool result = enumerable.SequenceEqualOrNull(other); + + // Then + Assert.False(result); + } + + [Fact(DisplayName = "IEnumerable.SequenceEqualOrNull should return true when the current enumerable is equal to the other enumerable.")] + public void SequenceEqualOrNullShouldReturnTrueWhenCurrentEnumerableIsEqualToTheOtherEnumerable() + { + // Given + IEnumerable? enumerable = [1, 2, 3]; + IEnumerable other = [1, 2, 3]; + + // When + bool result = enumerable.SequenceEqualOrNull(other); + + // Then + Assert.True(result); + } + + [Fact(DisplayName = "IEnumerable.SequenceEqualOrNull should return false when the current enumerable is not equal to the other enumerable.")] + public void SequenceEqualOrNullShouldReturnTrueWhenCurrentEnumerableIsNotEqualToTheOtherEnumerable() + { + // Given + IEnumerable? enumerable = [1, 2, 3]; + IEnumerable other = [3, 2, 1]; + + // When + bool result = enumerable.SequenceEqualOrNull(other); + + // Then + Assert.False(result); + } + [Fact(DisplayName = "IEnumerable.SingleOrNone should return success none when the enumerable contains no elements.")] public void SingleOrNoneShouldReturnSuccessNoneWhenEnumerableContainsNoElements() { diff --git a/OnixLabs.Core.UnitTests/ObjectExtensionTests.cs b/OnixLabs.Core.UnitTests/ObjectExtensionTests.cs index d8fe84f..aeb1a46 100644 --- a/OnixLabs.Core.UnitTests/ObjectExtensionTests.cs +++ b/OnixLabs.Core.UnitTests/ObjectExtensionTests.cs @@ -50,6 +50,68 @@ public void IsWithinRangeExclusiveShouldProduceExpectedResult(int value, int min Assert.Equal(expected, actual); } + [Fact(DisplayName = "CompareToObject should produce zero if the current IComparable is equal to the specified object.")] + public void CompareToObjectShouldProduceZeroIfTheCurrentIComparableIsEqualToTheSpecifiedObject() + { + // Given + const int expected = 0; + + // When + int actual = 123.CompareToObject(123); + + // Then + Assert.Equal(expected, actual); + } + + [Fact(DisplayName = "CompareToObject should produce positive one if the specified object is null.")] + public void CompareToObjectShouldProducePositiveOneIfTheSpecifiedObjectIsNull() + { + // Given + const int expected = 1; + + // When + int actual = 124.CompareToObject(null); + + // Then + Assert.Equal(expected, actual); + } + + [Fact(DisplayName = "CompareToObject should produce positive one if the current IComparable greater than the specified object.")] + public void CompareToObjectShouldProducePositiveOneIfTheCurrentIComparableIsGreaterThanTheSpecifiedObject() + { + // Given + const int expected = 1; + + // When + int actual = 124.CompareToObject(123); + + // Then + Assert.Equal(expected, actual); + } + + [Fact(DisplayName = "CompareToObject should produce negative one if the current IComparable greater than the specified object.")] + public void CompareToObjectShouldProduceNegativeOneIfTheCurrentIComparableIsGreaterThanTheSpecifiedObject() + { + // Given + const int expected = -1; + + // When + int actual = 122.CompareToObject(123); + + // Then + Assert.Equal(expected, actual); + } + + [Fact(DisplayName = "CompareToObject should throw ArgumentException if the specified object is of the incorrect type.")] + public void CompareToObjectShouldThrowArgumentExceptionIfSpecifiedObjectIsOfIncorrectType() + { + // When + Exception exception = Assert.Throws(() => 122.CompareToObject(123.456)); + + // Then + Assert.Equal("Object must be of type System.Int32 (Parameter 'obj')", exception.Message); + } + [Fact(DisplayName = "ToRecordString should produce a record formatted string")] public void ToRecordStringShouldProduceExpectedResult() { diff --git a/OnixLabs.Core.UnitTests/OnixLabs.Core.UnitTests.csproj b/OnixLabs.Core.UnitTests/OnixLabs.Core.UnitTests.csproj index c332195..199ccab 100644 --- a/OnixLabs.Core.UnitTests/OnixLabs.Core.UnitTests.csproj +++ b/OnixLabs.Core.UnitTests/OnixLabs.Core.UnitTests.csproj @@ -6,13 +6,13 @@ enable - - - + + + runtime; build; native; contentfiles; analyzers; buildtransitive all - + runtime; build; native; contentfiles; analyzers; buildtransitive all diff --git a/OnixLabs.Core.UnitTests/OptionalTests.cs b/OnixLabs.Core.UnitTests/OptionalTests.cs index 6621a34..eed14d1 100644 --- a/OnixLabs.Core.UnitTests/OptionalTests.cs +++ b/OnixLabs.Core.UnitTests/OptionalTests.cs @@ -201,39 +201,51 @@ public void OptionalNoneExplicitOperatorShouldProduceExpectedResult() public void OptionalSomeValuesShouldBeConsideredEqual() { // Given - Optional a = Optional.Some(123); - Optional b = Optional.Some(123); + Some a = Optional.Some(123); + Some b = Optional.Some(123); + object bObject = b; // When / Then Assert.Equal(a, b); - Assert.True(a == b); + Assert.Equal(a, bObject); Assert.True(a.Equals(b)); + Assert.True(a.Equals(bObject)); + Assert.True(a == b); + Assert.False(a != b); } [Fact(DisplayName = "Optional Some values should not be considered equal.")] public void OptionalSomeValuesShouldNotBeConsideredEqual() { // Given - Optional a = Optional.Some(123); - Optional b = Optional.Some(456); + Some a = Optional.Some(123); + Some b = Optional.Some(456); + object bObject = b; // When / Then Assert.NotEqual(a, b); - Assert.True(a != b); + Assert.NotEqual(a, bObject); Assert.False(a.Equals(b)); + Assert.False(a.Equals(bObject)); + Assert.False(a == b); + Assert.True(a != b); } [Fact(DisplayName = "Optional None values should be considered equal.")] public void OptionalNoneValuesShouldBeConsideredEqual() { // Given - Optional a = Optional.None; - Optional b = Optional.None; + None a = Optional.None; + None b = Optional.None; + object bObject = b; // When / Then Assert.Equal(a, b); - Assert.True(a == b); + Assert.Equal(a, bObject); Assert.True(a.Equals(b)); + Assert.True(a.Equals(bObject)); + Assert.True(a == b); + Assert.False(a != b); } [Fact(DisplayName = "Optional Some and None values should not be considered equal.")] @@ -242,11 +254,15 @@ public void OptionalSomeAndNoneValuesShouldNotBeConsideredEqual() // Given Optional a = Optional.Some(123); Optional b = Optional.None; + object bObject = b; // When / Then Assert.NotEqual(a, b); - Assert.True(a != b); + Assert.NotEqual(a, bObject); Assert.False(a.Equals(b)); + Assert.False(a.Equals(bObject)); + Assert.False(a == b); + Assert.True(a != b); } [Fact(DisplayName = "Optional Some.GetHashCode should produce the expected result.")] @@ -317,7 +333,8 @@ public void OptionalSomeGetValueOrDefaultWithDefaultValueShouldProduceExpectedRe Optional text = "abc"; // When - int actualNumber = number.GetValueOrDefault(456); + int? actualNumber = number.GetValueOrDefault(456); + // ReSharper disable once VariableCanBeNotNullable string? actualText = text.GetValueOrDefault("xyz"); // Then diff --git a/OnixLabs.Core.UnitTests/PreconditionTests.cs b/OnixLabs.Core.UnitTests/PreconditionTests.cs index 8990fed..a8093b2 100644 --- a/OnixLabs.Core.UnitTests/PreconditionTests.cs +++ b/OnixLabs.Core.UnitTests/PreconditionTests.cs @@ -30,6 +30,13 @@ public void CheckShouldThrowInvalidOperationExceptionWhenConditionIsFalse() Assert.Equal("Argument must satisfy the specified condition.", exception.Message); } + [Fact(DisplayName = "Check should not throw an InvalidOperationException when the condition is true")] + public void CheckShouldNotThrowInvalidOperationExceptionWhenConditionIsTrue() + { + // Given / When / Then + Check(true); + } + [Fact(DisplayName = "CheckNotNull should throw an InvalidOperationException when the condition is null")] public void CheckNotNullShouldThrowInvalidOperationExceptionWhenConditionIsNull() { @@ -40,6 +47,13 @@ public void CheckNotNullShouldThrowInvalidOperationExceptionWhenConditionIsNull( Assert.Equal("Argument must not be null.", exception.Message); } + [Fact(DisplayName = "CheckNotNull should not throw an InvalidOperationException when the condition is not null")] + public void CheckNotNullShouldNotThrowInvalidOperationExceptionWhenConditionIsNotNull() + { + // Given / When / Then + CheckNotNull(new object()); + } + [Fact(DisplayName = "CheckNotNullOrEmpty should throw an InvalidOperationException when the value is null")] public void CheckNotNullOrEmptyShouldThrowInvalidOperationExceptionWhenValueIsNull() { @@ -60,6 +74,19 @@ public void CheckNotNullOrEmptyShouldThrowInvalidOperationExceptionWhenValueIsEm Assert.Equal("Argument must not be null or empty.", exception.Message); } + [Fact(DisplayName = "CheckNotNullOrEmpty should return the argument value when the value is not null and not empty")] + public void CheckNotNullShouldReturnArgumentValueWhenValueIsNotNullAndNotEmpty() + { + // Given + const string expected = "Hello, World!"; + + // When + string actual = CheckNotNullOrEmpty(expected); + + // Then + Assert.Equal(expected, actual); + } + [Fact(DisplayName = "CheckNotNullOrWhiteSpace should throw an InvalidOperationException when the value is null")] public void CheckNotNullOrWhiteSpaceShouldThrowInvalidOperationExceptionWhenValueIsNull() { @@ -80,6 +107,19 @@ public void CheckNotNullOrWhiteSpaceShouldThrowInvalidOperationExceptionWhenValu Assert.Equal("Argument must not be null or whitespace.", exception.Message); } + [Fact(DisplayName = "CheckNotNullOrWhiteSpace should return the argument value when the value is not null and not whitespace")] + public void CheckNotNullShouldReturnArgumentValueWhenValueIsNotNullAndNotWhiteSpace() + { + // Given + const string expected = "Hello, World!"; + + // When + string actual = CheckNotNullOrWhiteSpace(expected); + + // Then + Assert.Equal(expected, actual); + } + [Fact(DisplayName = "Require should throw an ArgumentException when the condition is false")] public void RequireShouldThrowArgumentExceptionWhenConditionIsFalse() { @@ -90,6 +130,13 @@ public void RequireShouldThrowArgumentExceptionWhenConditionIsFalse() Assert.Equal("Argument must satisfy the specified condition.", exception.Message); } + [Fact(DisplayName = "Require should not throw an InvalidOperationException when the condition is true")] + public void RequireShouldNotThrowInvalidOperationExceptionWhenConditionIsTrue() + { + // Given / When / Then + Require(true); + } + [Fact(DisplayName = "RequireWithinRange should throw an ArgumentOutOfRangeException when the condition is false")] public void RequireWithinRangeShouldThrowArgumentOutOfRangeExceptionWhenConditionIsFalse() { @@ -100,6 +147,14 @@ public void RequireWithinRangeShouldThrowArgumentOutOfRangeExceptionWhenConditio Assert.Equal("Argument must be within range.", exception.Message); } + [Theory(DisplayName = "RequireWithinRange should not throw an ArgumentOutOfRangeException when the condition is true")] + [InlineData(100, 0, 123)] + public void RequireWithinRangeShouldNotThrowArgumentOutOfRangeExceptionWhenConditionIsTrue(int value, int min, int max) + { + // Given / When / Then + RequireWithinRange(value >= min && value <= max); + } + [Fact(DisplayName = "RequireWithinRangeInclusive should throw an ArgumentOutOfRangeException when the value falls below the specified range")] public void RequireWithinRangeInclusiveShouldThrowArgumentOutOfRangeExceptionWhenValueFallsBelowSpecifiedRange() { @@ -120,6 +175,27 @@ public void RequireWithinRangeInclusiveShouldThrowArgumentOutOfRangeExceptionWhe Assert.Equal("Argument must be within range.", exception.Message); } + [Fact(DisplayName = "RequireWithinRangeInclusive should not throw an ArgumentOutOfRangeException when the value is exactly the minimum value")] + public void RequireWithinRangeInclusiveShouldNotThrowArgumentOutOfRangeExceptionWhenValueIsExactlyTheMinimumValue() + { + // Given / When / Then + RequireWithinRangeInclusive(1, 1, 3); + } + + [Fact(DisplayName = "RequireWithinRangeInclusive should not throw an ArgumentOutOfRangeException when the value is exactly the maximum value")] + public void RequireWithinRangeInclusiveShouldNotThrowArgumentOutOfRangeExceptionWhenValueIsExactlyTheMaximumValue() + { + // Given / When / Then + RequireWithinRangeInclusive(3, 1, 3); + } + + [Fact(DisplayName = "RequireWithinRangeInclusive should not throw an ArgumentOutOfRangeException when the value is between the specified range")] + public void RequireWithinRangeInclusiveShouldNotThrowArgumentOutOfRangeExceptionWhenValueIsBetweenSpecifiedRange() + { + // Given / When / Then + RequireWithinRangeInclusive(2, 1, 3); + } + [Fact(DisplayName = "RequireWithinRangeExclusive should throw an ArgumentOutOfRangeException when the value falls below the specified range")] public void RequireWithinRangeExclusiveShouldThrowArgumentOutOfRangeExceptionWhenValueFallsBelowSpecifiedRange() { @@ -140,6 +216,13 @@ public void RequireWithinRangeExclusiveShouldThrowArgumentOutOfRangeExceptionWhe Assert.Equal("Argument must be within range.", exception.Message); } + [Fact(DisplayName = "RequireWithinRangeExclusive should not throw an ArgumentOutOfRangeException when the value falls between the specified range")] + public void RequireWithinRangeExclusiveShouldThrowArgumentOutOfRangeExceptionWhenValueFallsBetweenSpecifiedRange() + { + // Given / When / Then + RequireWithinRangeExclusive(3, 2, 4); + } + [Fact(DisplayName = "RequireNotNull should throw an ArgumentNullException when the condition is null")] public void RequireNotNullShouldThrowArgumentNullExceptionWhenConditionIsNull() { @@ -150,6 +233,13 @@ public void RequireNotNullShouldThrowArgumentNullExceptionWhenConditionIsNull() Assert.Equal("Argument must not be null.", exception.Message); } + [Fact(DisplayName = "RequireNotNull should not throw an InvalidOperationException when the condition is not null")] + public void RequireNotNullShouldNotThrowInvalidOperationExceptionWhenConditionIsNotNull() + { + // Given / When / Then + RequireNotNull(new object()); + } + [Fact(DisplayName = "RequireNotNullOrEmpty should throw an ArgumentException when the value is null")] public void RequireNotNullOrEmptyShouldThrowArgumentExceptionWhenValueIsNull() { @@ -170,6 +260,19 @@ public void RequireNotNullOrEmptyShouldThrowArgumentExceptionWhenValueIsEmpty() Assert.Equal("Argument must not be null or empty.", exception.Message); } + [Fact(DisplayName = "RequireNotNullOrEmpty should return the argument value when the value is not null and not empty")] + public void RequireNotNullShouldReturnArgumentValueWhenValueIsNotNullAndNotEmpty() + { + // Given + const string expected = "Hello, World!"; + + // When + string actual = RequireNotNullOrEmpty(expected); + + // Then + Assert.Equal(expected, actual); + } + [Fact(DisplayName = "RequireNotNullOrWhiteSpace should throw an ArgumentException when the value is null")] public void RequireNotNullOrWhiteSpaceShouldThrowArgumentExceptionWhenValueIsNull() { @@ -190,6 +293,19 @@ public void RequireNotNullOrWhiteSpaceShouldThrowArgumentExceptionWhenValueIsWhi Assert.Equal("Argument must not be null or whitespace.", exception.Message); } + [Fact(DisplayName = "RequireNotNullOrWhiteSpace should return the argument value when the value is not null and not whitespace")] + public void RequireNotNullShouldReturnArgumentValueWhenValueIsNotNullAndNotWhiteSpace() + { + // Given + const string expected = "Hello, World!"; + + // When + string actual = RequireNotNullOrWhiteSpace(expected); + + // Then + Assert.Equal(expected, actual); + } + [Fact(DisplayName = "RequireIsDefined should throw an ArgumentOutOfRangeException when the specified enum value is not defined")] public void RequireIsDefinedShouldThrowArgumentOutOfRangeExceptionWhenSpecifiedEnumValueIsNotDefined() { @@ -199,4 +315,11 @@ public void RequireIsDefinedShouldThrowArgumentOutOfRangeExceptionWhenSpecifiedE // Then Assert.Equal("Invalid Shape enum value: 2. Valid values include: Square, Circle.", exception.Message); } + + [Fact(DisplayName = "RequireIsDefined should not throw an ArgumentOutOfRangeException when the specified enum value is defined")] + public void RequireIsDefinedShouldNotThrowArgumentOutOfRangeExceptionWhenSpecifiedEnumValueIsDefined() + { + // Given / When / Then + RequireIsDefined((Shape)1); + } } diff --git a/OnixLabs.Core.UnitTests/ResultEqualityComparerTests.cs b/OnixLabs.Core.UnitTests/ResultEqualityComparerTests.cs index cce95ae..d90a863 100644 --- a/OnixLabs.Core.UnitTests/ResultEqualityComparerTests.cs +++ b/OnixLabs.Core.UnitTests/ResultEqualityComparerTests.cs @@ -156,6 +156,21 @@ public void ResultEqualityComparerEqualsShouldReturnFalseWhenComparingNonIdentic Assert.False(result2); } + [Fact(DisplayName = "ResultEqualityComparer.GetHashCode should produce the expected result")] + public void ResultEqualityComparerEqualsShouldReturnExpectedResult() + { + // Given + int expected = "abc".GetHashCode(); + Result value = "abc"; + ResultEqualityComparer comparer = new(); + + // When + int actual = comparer.GetHashCode(value); + + // Then + Assert.Equal(expected, actual); + } + private sealed class CaseInsensitiveStringComparer : EqualityComparer { public override bool Equals(string? x, string? y) => string.Equals(x, y, StringComparison.InvariantCultureIgnoreCase); diff --git a/OnixLabs.Core.UnitTests/ResultExtensionTests.cs b/OnixLabs.Core.UnitTests/ResultExtensionTests.cs index 236d2de..f3826d8 100644 --- a/OnixLabs.Core.UnitTests/ResultExtensionTests.cs +++ b/OnixLabs.Core.UnitTests/ResultExtensionTests.cs @@ -13,12 +13,128 @@ // limitations under the License. using System; +using System.Threading.Tasks; using Xunit; namespace OnixLabs.Core.UnitTests; public sealed class ResultExtensionTests { + [Fact(DisplayName = "Result.GetValueOrDefaultAsync should return the result value when the result is Success")] + public async Task ResultGetValueOrDefaultAsyncShouldReturnResultValueWhenResultIsSuccess() + { + // Given + const int expectedNumber = 123; + const string expectedText = "abc"; + Task> numberTask = Result.OfAsync(async () => await Task.FromResult(expectedNumber)); + Task> textTask = Result.OfAsync(async () => await Task.FromResult(expectedText)); + + // When + int actualNumber = await numberTask.GetValueOrDefaultAsync(); + string? actualText = await textTask.GetValueOrDefaultAsync(); + + // Then + Assert.Equal(expectedNumber, actualNumber); + + Assert.NotNull(actualText); + Assert.Equal(expectedText, actualText); + } + + [Fact(DisplayName = "Result.GetValueOrDefaultAsync should return default when the result is Failure")] + public async Task ResultGetValueOrDefaultAsyncShouldReturnDefaultWhenResultIsFailure() + { + // Given + Exception exception = new("failure"); + Task> numberTask = Result.OfAsync(() => throw exception); + Task> textTask = Result.OfAsync(() => throw exception); + + // When + int actualNumber = await numberTask.GetValueOrDefaultAsync(); + string? actualText = await textTask.GetValueOrDefaultAsync(); + + // Then + Assert.Equal(default, actualNumber); + Assert.Null(actualText); + } + + [Fact(DisplayName = "Result.GetValueOrDefaultAsync with default value should return the result value when the result is Success")] + public async Task ResultGetValueOrDefaultAsyncWithDefaultValueShouldReturnResultValueWhenResultIsSuccess() + { + // Given + const int expectedNumber = 123; + const string expectedText = "abc"; + Task> numberTask = Result.OfAsync(async () => await Task.FromResult(expectedNumber)); + Task> textTask = Result.OfAsync(async () => await Task.FromResult(expectedText)); + + // When + int actualNumber = await numberTask.GetValueOrDefaultAsync(456); + string? actualText = await textTask.GetValueOrDefaultAsync("xyz"); + + // Then + Assert.Equal(expectedNumber, actualNumber); + + Assert.NotNull(actualText); + Assert.Equal(expectedText, actualText); + } + + [Fact(DisplayName = "Result.GetValueOrDefaultAsync with default value should return default value when the result is Failure")] + public async Task ResultGetValueOrDefaultAsyncWithDefaultValueShouldReturnDefaultValueWhenResultIsFailure() + { + // Given + const int expectedNumber = 456; + const string expectedText = "abc"; + Exception exception = new("failure"); + Task> numberTask = Result.OfAsync(() => throw exception); + Task> textTask = Result.OfAsync(() => throw exception); + + // When + int actualNumber = await numberTask.GetValueOrDefaultAsync(expectedNumber); + string actualText = await textTask.GetValueOrDefaultAsync(expectedText); + + // Then + Assert.Equal(expectedNumber, actualNumber); + + Assert.NotNull(actualText); + Assert.Equal(expectedText, actualText); + } + + [Fact(DisplayName = "Result.GetValueOrThrowAsync should return the result value when the result is Success")] + public async Task ResultGetValueOrThrowAsyncShouldReturnResultValueWhenResultIsSuccess() + { + // Given + const int expectedNumber = 123; + const string expectedText = "abc"; + Task> numberTask = Result.OfAsync(async () => await Task.FromResult(expectedNumber)); + Task> textTask = Result.OfAsync(async () => await Task.FromResult(expectedText)); + + // When + int actualNumber = await numberTask.GetValueOrThrowAsync(); + string actualText = await textTask.GetValueOrThrowAsync(); + + // Then + Assert.Equal(expectedNumber, actualNumber); + + Assert.NotNull(actualText); + Assert.Equal(expectedText, actualText); + } + + [Fact(DisplayName = "Result.GetValueOrThrowAsync should throw Exception when the result is Failure")] + public async Task ResultGetValueOrThrowAsyncShouldReturnDefaultWhenResultIsFailure() + { + // Given + Exception exception = new("failure"); + Task> numberTask = Result.OfAsync(() => throw exception); + Task> textTask = Result.OfAsync(() => throw exception); + + // When + Exception numberException = await Assert.ThrowsAsync(async () => await numberTask.GetValueOrThrowAsync()); + Exception textException = await Assert.ThrowsAsync(async () => await textTask.GetValueOrThrowAsync()); + + // Then + Assert.Equal(numberException, exception); + Assert.Equal(textException, exception); + } + [Fact(DisplayName = "Result.GetValueOrNone should return the Optional value when the result is Success and the Optional value is not None")] public void ResultGetValueOrNoneShouldReturnOptionalValueWhenResultIsSuccessAndOptionalValueIsNotNone() { @@ -73,6 +189,60 @@ public void ResultGetValueOrNoneShouldReturnNoneWhenResultIsFailure() Assert.Equal(expectedText, actualText); } + [Fact(DisplayName = "Result.GetValueOrNoneAsync should return the Optional value when the result is Success and the Optional value is not None")] + public async Task ResultGetValueOrNoneAsyncShouldReturnOptionalValueWhenResultIsSuccessAndOptionalValueIsNotNone() + { + // Given + Optional expectedNumber = 123; + Optional expectedText = "abc"; + Result> numberResult = expectedNumber; + Result> textResult = expectedText; + + // When + Optional actualNumber = await Task.FromResult(numberResult).GetValueOrNoneAsync(); + Optional actualText = await Task.FromResult(textResult).GetValueOrNoneAsync(); + + // Then + Assert.Equal(expectedNumber, actualNumber); + Assert.Equal(expectedText, actualText); + } + + [Fact(DisplayName = "Result.GetValueOrNoneAsync should return None value when the result is Success and the Optional value is None")] + public async Task ResultGetValueOrNoneAsyncShouldReturnNoneWhenResultIsSuccessAndOptionalValueIsNotNone() + { + // Given + Optional expectedNumber = Optional.None; + Optional expectedText = Optional.None; + Result> numberResult = expectedNumber; + Result> textResult = expectedText; + + // When + Optional actualNumber = await Task.FromResult(numberResult).GetValueOrNoneAsync(); + Optional actualText = await Task.FromResult(textResult).GetValueOrNoneAsync(); + + // Then + Assert.Equal(expectedNumber, actualNumber); + Assert.Equal(expectedText, actualText); + } + + [Fact(DisplayName = "Result.GetValueOrNoneAsync should return None value when the result is Failure")] + public async Task ResultGetValueOrNoneAsyncShouldReturnNoneWhenResultIsFailure() + { + // Given + Optional expectedNumber = Optional.None; + Optional expectedText = Optional.None; + Result> numberResult = Result>.Failure(new Exception("Result has failed.")); + Result> textResult = Result>.Failure(new Exception("Result has failed.")); + + // When + Optional actualNumber = await Task.FromResult(numberResult).GetValueOrNoneAsync(); + Optional actualText = await Task.FromResult(textResult).GetValueOrNoneAsync(); + + // Then + Assert.Equal(expectedNumber, actualNumber); + Assert.Equal(expectedText, actualText); + } + [Fact(DisplayName = "Result.GetOptionalValueOrThrow should return the Optional value when the Result is Success and the Optional value is not None")] public void ResultGetOptionalValueOrThrowShouldReturnOptionalValueWhenResultIsSuccessAndOptionalValueIsNotNone() { @@ -123,6 +293,55 @@ public void ResultGetOptionalValueOrThrowShouldThrowInvalidOperationExceptionWhe Assert.Equal("Result has failed.", textException.Message); } + [Fact(DisplayName = "Result.GetOptionalValueOrThrowAsync should return the Optional value when the Result is Success and the Optional value is not None")] + public async Task ResultGetOptionalValueOrThrowAsyncShouldReturnOptionalValueWhenResultIsSuccessAndOptionalValueIsNotNone() + { + // Given + const int expectedNumber = 123; + const string expectedText = "abc"; + Result> numberResult = Optional.Some(expectedNumber); + Result> textResult = Optional.Some(expectedText); + + // When + int actualNumber = await Task.FromResult(numberResult).GetOptionalValueOrThrowAsync(); + string actualText = await Task.FromResult(textResult).GetOptionalValueOrThrowAsync(); + + // Then + Assert.Equal(expectedNumber, actualNumber); + Assert.Equal(expectedText, actualText); + } + + [Fact(DisplayName = "Result.GetOptionalValueOrThrowAsync should throw InvalidOperationException when the Result is Success and the Optional value is None")] + public async Task ResultGetOptionalValueOrThrowAsyncShouldThrowInvalidOperationExceptionWhenResultIsSuccessAndOptionalValueIsNone() + { + // Given + Result> numberResult = Optional.None; + Result> textResult = Optional.None; + + // When + Exception numberException = await Assert.ThrowsAsync(async () => await Task.FromResult(numberResult).GetOptionalValueOrThrowAsync()); + Exception textException = await Assert.ThrowsAsync(async () => await Task.FromResult(textResult).GetOptionalValueOrThrowAsync()); + + // Then + Assert.Equal("Optional value of type System.Int32 is not present.", numberException.Message); + Assert.Equal("Optional value of type System.String is not present.", textException.Message); + } + + [Fact(DisplayName = "Result.GetOptionalValueOrThrowAsync should throw InvalidOperationException when the Result is Failure")] + public async Task ResultGetOptionalValueOrThrowAsyncShouldThrowInvalidOperationExceptionWhenResultIsFailure() + { + // Given + Result> numberResult = Result>.Failure(new Exception("Result has failed.")); + Result> textResult = Result>.Failure(new Exception("Result has failed.")); + + // When + Exception numberException = await Assert.ThrowsAsync(async () => await Task.FromResult(numberResult).GetOptionalValueOrThrowAsync()); + Exception textException = await Assert.ThrowsAsync(async () => await Task.FromResult(textResult).GetOptionalValueOrThrowAsync()); + + // Then + Assert.Equal("Result has failed.", numberException.Message); + Assert.Equal("Result has failed.", textException.Message); + } [Fact(DisplayName = "Result.GetOptionalValueOrDefault should return the Optional value when the Result is Success and the Optional value is not None")] public void ResultGetOptionalValueOrDefaultShouldReturnOptionalValueWhenResultIsSuccessAndOptionalValueIsNotNone() @@ -143,7 +362,7 @@ public void ResultGetOptionalValueOrDefaultShouldReturnOptionalValueWhenResultIs } [Fact(DisplayName = "Result.GetOptionalValueOrDefault should throw return the default value when the Result is Success and the Optional value is None")] - public void ResultGetOptionalValueOrThrowShouldReturnDefaultValueWhenResultIsSuccessAndOptionalValueIsNone() + public void ResultGetOptionalValueOrDefaultShouldReturnDefaultValueWhenResultIsSuccessAndOptionalValueIsNone() { // Given const int expectedNumber = 456; @@ -177,4 +396,451 @@ public void ResultGetOptionalValueOrDefaultShouldReturnDefaultValueWhenResultIsF Assert.Equal(expectedNumber, actualNumber); Assert.Equal(expectedText, actualText); } + + + [Fact(DisplayName = "Result.GetOptionalValueOrDefaultAsync should return the Optional value when the Result is Success and the Optional value is not None")] + public async Task ResultGetOptionalValueOrDefaultAsyncShouldReturnOptionalValueWhenResultIsSuccessAndOptionalValueIsNotNone() + { + // Given + const int expectedNumber = 123; + const string expectedText = "abc"; + Result> numberResult = Optional.Some(expectedNumber); + Result> textResult = Optional.Some(expectedText); + + // When + int actualNumber = await Task.FromResult(numberResult).GetOptionalValueOrDefaultAsync(456); + string actualText = await Task.FromResult(textResult).GetOptionalValueOrDefaultAsync("xyz"); + + // Then + Assert.Equal(expectedNumber, actualNumber); + Assert.Equal(expectedText, actualText); + } + + [Fact(DisplayName = "Result.GetOptionalValueOrDefaultAsync should throw return the default value when the Result is Success and the Optional value is None")] + public async Task ResultGetOptionalValueOrDefaultAsyncShouldReturnDefaultValueWhenResultIsSuccessAndOptionalValueIsNone() + { + // Given + const int expectedNumber = 456; + const string expectedText = "xyz"; + Result> numberResult = Optional.None; + Result> textResult = Optional.None; + + // When + int actualNumber = await Task.FromResult(numberResult).GetOptionalValueOrDefaultAsync(expectedNumber); + string actualText = await Task.FromResult(textResult).GetOptionalValueOrDefaultAsync(expectedText); + + // Then + Assert.Equal(expectedNumber, actualNumber); + Assert.Equal(expectedText, actualText); + } + + [Fact(DisplayName = "Result.GetOptionalValueOrDefaultAsync should return default value when the result is Failure")] + public async Task ResultGetOptionalValueOrDefaultAsyncShouldReturnDefaultValueWhenResultIsFailure() + { + // Given + const int expectedNumber = 456; + const string expectedText = "xyz"; + Result> numberResult = Result>.Failure(new Exception("Result has failed.")); + Result> textResult = Result>.Failure(new Exception("Result has failed.")); + + // When + int actualNumber = await Task.FromResult(numberResult).GetOptionalValueOrDefaultAsync(expectedNumber); + string actualText = await Task.FromResult(textResult).GetOptionalValueOrDefaultAsync(expectedText); + + // Then + Assert.Equal(expectedNumber, actualNumber); + Assert.Equal(expectedText, actualText); + } + + [Fact(DisplayName = "Result Success.MatchAsync should execute the some action.")] + public async Task ResultSuccessMatchAsyncShouldExecuteSuccessAction() + { + // Given + bool successCalled = false; + Result result = Result.Success(); + + // When + await Task.FromResult(result).MatchAsync(success: () => { successCalled = true; }); + + // Then + Assert.True(successCalled); + } + + [Fact(DisplayName = "Result Failure.MatchAsync should execute the none action.")] + public async Task ResultFailureMatchAsyncShouldExecuteFailureAction() + { + // Given + bool failureCalled = false; + Exception exception = new("failure"); + Result result = Result.Failure(exception); + + // When + await Task.FromResult(result).MatchAsync(failure: _ => { failureCalled = true; }); + + // Then + Assert.True(failureCalled); + } + + [Fact(DisplayName = "Result Success.MatchAsync should produce the expected result.")] + public async Task ResultSuccessMatchAsyncShouldProduceExpectedResult() + { + // Given + const int expected = 9; + Result result = Result.Success(); + + // When + int actual = await Task.FromResult(result).MatchAsync( + success: () => 9, + failure: _ => 0 + ); + + // Then + Assert.Equal(expected, actual); + } + + [Fact(DisplayName = "Result Failure.MatchAsync should produce the expected result.")] + public async Task ResultFailureMatchAsyncShouldProduceExpectedResult() + { + // Given + const int expected = 0; + Exception exception = new("failure"); + Result result = Result.Failure(exception); + + // When + int actual = await Task.FromResult(result).MatchAsync( + success: () => 9, + failure: _ => 0 + ); + + // Then + Assert.Equal(expected, actual); + } + + [Fact(DisplayName = "Result Success.MatchAsync should execute the some action.")] + public async Task ResultOfTSuccessMatchAsyncShouldExecuteSuccessAction() + { + // Given + bool successCalled = false; + Result result = 123; + + // When + await Task.FromResult(result).MatchAsync(success: _ => { successCalled = true; }); + + // Then + Assert.True(successCalled); + } + + [Fact(DisplayName = "Result Failure.MatchAsync should execute the none action.")] + public async Task ResultOfTFailureMatchAsyncShouldExecuteFailureAction() + { + // Given + bool failureCalled = false; + Exception exception = new("failure"); + Result result = Result.Failure(exception); + + // When + await Task.FromResult(result).MatchAsync(failure: _ => { failureCalled = true; }); + + // Then + Assert.True(failureCalled); + } + + [Fact(DisplayName = "Result Success.MatchAsync should produce the expected result.")] + public async Task ResultOfTSuccessMatchAsyncShouldProduceExpectedResult() + { + // Given + const int expected = 9; + Result result = 3; + + // When + int actual = await Task.FromResult(result).MatchAsync( + success: value => value * value, + failure: _ => 0 + ); + + // Then + Assert.Equal(expected, actual); + } + + [Fact(DisplayName = "Result Failure.MatchAsync should produce the expected result.")] + public async Task ResultOfTFailureMatchAsyncShouldProduceExpectedResult() + { + // Given + const int expected = 0; + Exception exception = new("failure"); + Result result = Result.Failure(exception); + + // When + int actual = await Task.FromResult(result).MatchAsync( + success: value => value * value, + failure: _ => 0 + ); + + // Then + Assert.Equal(expected, actual); + } + + [Fact(DisplayName = "Result Success.SelectAsync should produce the expected result")] + public async Task ResultSuccessSelectAsyncShouldProduceExpectedResult() + { + // Given + Result expected = Result.Success(); + + // When + Result actual = await Task.FromResult(expected).SelectAsync(() => { }); + + // Then + Assert.Equal(expected, actual); + } + + [Fact(DisplayName = "Result Failure.SelectAsync should produce the expected result")] + public async Task ResultFailureSelectAsyncShouldProduceExpectedResult() + { + // Given + Result expected = Result.Failure(new Exception("Failure")); + + // When + Result actual = await Task.FromResult(expected).SelectAsync(() => { }); + + // Then + Assert.Equal(expected, actual); + } + + [Fact(DisplayName = "Result Success.SelectAsync should produce the expected result")] + public async Task ResultSuccessSelectAsyncTResultShouldProduceExpectedResult() + { + // Given + const int expected = 9; + Result result = Result.Success(); + + // When + Result actual = await Task.FromResult(result).SelectAsync(() => 9); + + // Then + Assert.Equal(expected, actual); + } + + [Fact(DisplayName = "Result Failure.SelectAsync should produce the expected result")] + public async Task ResultFailureSelectAsyncTResultShouldProduceExpectedResult() + { + // Given + Exception exception = new("failure"); + Result result = Result.Failure(exception); + + // When + Result actual = await Task.FromResult(result).SelectAsync(() => 9); + + // Then + Assert.Equal(Result.Failure(exception), actual); + } + + [Fact(DisplayName = "Result Success.SelectAsync should produce the expected result")] + public async Task ResultOfTSuccessSelectAsyncShouldProduceExpectedResult() + { + // Given + Result expected = Result.Success(); + Result result = 123; + + // When + Result actual = await Task.FromResult(result).SelectAsync(_ => { }); + + // Then + Assert.Equal(expected, actual); + } + + [Fact(DisplayName = "Result Failure.SelectAsync should produce the expected result")] + public async Task ResultOfTFailureSelectAsyncShouldProduceExpectedResult() + { + // Given + Exception exception = new("Failure"); + Result expected = Result.Failure(exception); + Result result = Result.Failure(exception); + + // When + Result actual = await Task.FromResult(result).SelectAsync(_ => { }); + + // Then + Assert.Equal(expected, actual); + } + + [Fact(DisplayName = "Result Success.SelectAsync should produce the expected result")] + public async Task ResultOfTSuccessSelectAsyncTResultShouldProduceExpectedResult() + { + // Given + Result expected = 9; + Result result = 3; + + // When + Result actual = await Task.FromResult(result).SelectAsync(x => x * x); + + // Then + Assert.Equal(expected, actual); + } + + [Fact(DisplayName = "Result Failure.SelectAsync should produce the expected result")] + public async Task ResultOfTFailureSelectAsyncTResultShouldProduceExpectedResult() + { + // Given + Exception exception = new("failure"); + Result result = Result.Failure(exception); + + // When + Result actual = await Task.FromResult(result).SelectAsync(x => x * x); + + // Then + Assert.Equal(Result.Failure(exception), actual); + } + + [Fact(DisplayName = "Result Success.SelectManyAsync should produce the expected result")] + public async Task ResultSuccessSelectManyAsyncShouldProduceExpectedResult() + { + // Given + Result expected = Result.Success(); + + // When + Result actual = await Task.FromResult(expected).SelectManyAsync(Result.Success); + + // Then + Assert.Equal(expected, actual); + } + + [Fact(DisplayName = "Result Failure.SelectManyAsync should produce the expected result")] + public async Task ResultFailureSelectManyAsyncShouldProduceExpectedResult() + { + // Given + Result expected = Result.Failure(new Exception("Failure")); + + // When + Result actual = await Task.FromResult(expected).SelectManyAsync(Result.Success); + + // Then + Assert.Equal(expected, actual); + } + + [Fact(DisplayName = "Result Success.SelectManyAsync should produce the expected result")] + public async Task ResultSuccessSelectManyAsyncTResultShouldProduceExpectedResult() + { + // Given + const int expected = 9; + Result result = Result.Success(); + + // When + Result actual = await Task.FromResult(result).SelectManyAsync(() => 9); + + // Then + Assert.Equal(expected, actual); + } + + [Fact(DisplayName = "Result Failure.SelectManyAsync should produce the expected result")] + public async Task ResultFailureSelectManyAsyncTResultShouldProduceExpectedResult() + { + // Given + Exception exception = new("failure"); + Result result = Result.Failure(exception); + + // When + Result actual = await Task.FromResult(result).SelectManyAsync(() => 9); + + // Then + Assert.Equal(Result.Failure(exception), actual); + } + + [Fact(DisplayName = "Result Success.SelectManyAsync should produce the expected result")] + public async Task ResultOfTSuccessSelectManyAsyncShouldProduceExpectedResult() + { + // Given + Result expected = Result.Success(); + Result result = 3; + + // When + Result actual = await Task.FromResult(result).SelectManyAsync(_ => Result.Success()); + + // Then + Assert.Equal(expected, actual); + } + + [Fact(DisplayName = "Result Failure.SelectManyAsync should produce the expected result")] + public async Task ResultOfTFailureSelectManyAsyncShouldProduceExpectedResult() + { + // Given + Exception exception = new("Failure"); + Result expected = Result.Failure(exception); + Result result = Result.Failure(exception); + + // When + Result actual = await Task.FromResult(result).SelectManyAsync(_ => Result.Success()); + + // Then + Assert.Equal(expected, actual); + } + + [Fact(DisplayName = "Result Success.SelectManyAsync should produce the expected result")] + public async Task ResultOfTSuccessSelectManyAsyncTResultShouldProduceExpectedResult() + { + // Given + Result expected = 9; + Result result = 3; + + // When + Result actual = await Task.FromResult(result).SelectManyAsync(x => Result.Success(x * x)); + + // Then + Assert.Equal(expected, actual); + } + + [Fact(DisplayName = "Result Failure.SelectManyAsync should produce the expected result")] + public async Task ResultOfTFailureSelectManyAsyncTResultShouldProduceExpectedResult() + { + // Given + Exception exception = new("failure"); + Result result = Result.Failure(exception); + + // When + Result actual = await Task.FromResult(result).SelectManyAsync(x => Result.Success(x * x)); + + // Then + Assert.Equal(Result.Failure(exception), actual); + } + + [Fact(DisplayName = "Result Success.ThrowAsync should do nothing")] + public async Task ResultSuccessThrowAsyncShouldDoNothing() + { + // Given + Result result = Result.Success(); + + // When / Then + await Task.FromResult(result).ThrowAsync(); + } + + [Fact(DisplayName = "Result Failure.Throw should throw Exception")] + public async Task ResultFailureThrowAsyncShouldThrowException() + { + // Given + Exception exception = new("failure"); + Result result = Result.Failure(exception); + + // When / Then + await Assert.ThrowsAsync(async () => await Task.FromResult(result).ThrowAsync()); + } + + [Fact(DisplayName = "Result Success.ThrowAsync should do nothing")] + public async Task ResultOfTSuccessThrowAsyncShouldDoNothing() + { + // Given + Result result = Result.Success(123); + + // When / Then + await Task.FromResult(result).ThrowAsync(); + } + + [Fact(DisplayName = "Result Failure.ThrowAsync should throw Exception")] + public async Task ResultOfTFailureThrowAsyncShouldThrowException() + { + // Given + Exception exception = new("failure"); + Result result = Result.Failure(exception); + + // When / Then + await Assert.ThrowsAsync(async () => await Task.FromResult(result).ThrowAsync()); + } } diff --git a/OnixLabs.Core.UnitTests/ResultGenericTests.cs b/OnixLabs.Core.UnitTests/ResultGenericTests.cs new file mode 100644 index 0000000..f756333 --- /dev/null +++ b/OnixLabs.Core.UnitTests/ResultGenericTests.cs @@ -0,0 +1,743 @@ +// Copyright 2020 ONIXLabs +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +using System; +using System.Threading; +using System.Threading.Tasks; +using OnixLabs.Core.UnitTests.Data; +using Xunit; + +namespace OnixLabs.Core.UnitTests; + +public sealed class ResultGenericTests +{ + [Fact(DisplayName = "Result.Of should produce expected success result")] + public void ResultOfShouldProduceExpectedSuccessResult() + { + // Given / When + Result number = Result.Of(() => 123); + Result text = Result.Of(() => "abc"); + + // Then + Assert.True(number.IsSuccess); + Assert.False(number.IsFailure); + Assert.IsType>(number); + Assert.Equal(123, number); + + Assert.True(text.IsSuccess); + Assert.False(text.IsFailure); + Assert.IsType>(text); + Assert.Equal("abc", text); + } + + [Fact(DisplayName = "Result.Of should produce expected failure result")] + public void ResultOfShouldProduceExpectedFailureResult() + { + // Given / When + Exception exception = new("failure"); + Result number = Result.Of(() => throw exception); + Result text = Result.Of(() => throw exception); + + // Then + Assert.False(number.IsSuccess); + Assert.True(number.IsFailure); + Assert.IsType>(number); + Assert.Equal("failure", (number as Failure)!.Exception.Message); + + Assert.False(text.IsSuccess); + Assert.True(text.IsFailure); + Assert.IsType>(text); + Assert.Equal("failure", (text as Failure)!.Exception.Message); + } + + [Fact(DisplayName = "Result.OfAsync should produce expected success result")] + public async Task ResultOfAsyncShouldProduceExpectedSuccessResult() + { + // Given / When + Result number = await Result.OfAsync(async () => await Task.FromResult(123)); + Result text = await Result.OfAsync(async () => await Task.FromResult("abc")); + + // Then + Assert.True(number.IsSuccess); + Assert.False(number.IsFailure); + Assert.IsType>(number); + Assert.Equal(123, number); + + Assert.True(text.IsSuccess); + Assert.False(text.IsFailure); + Assert.IsType>(text); + Assert.Equal("abc", text); + } + + [Fact(DisplayName = "Result.OfAsync should produce expected failure result")] + public async Task ResultOfAsyncShouldProduceExpectedFailureResult() + { + // Given / When + Exception exception = new("failure"); + Result number = await Result.OfAsync(() => throw exception); + Result text = await Result.OfAsync(() => throw exception); + + // Then + Assert.False(number.IsSuccess); + Assert.True(number.IsFailure); + Assert.IsType>(number); + Assert.Equal("failure", (number as Failure)!.Exception.Message); + + Assert.False(text.IsSuccess); + Assert.True(text.IsFailure); + Assert.IsType>(text); + Assert.Equal("failure", (text as Failure)!.Exception.Message); + } + + [Fact(DisplayName = "Result.OfAsync with cancellation token should produce expected success result")] + public async Task ResultOfAsyncWithCancellationTokenShouldProduceExpectedSuccessResult() + { + // Given / When + CancellationToken token = CancellationToken.None; + Result number = await Result.OfAsync(async _ => await Task.FromResult(123), token); + Result text = await Result.OfAsync(async _ => await Task.FromResult("abc"), token); + + // Then + Assert.True(number.IsSuccess); + Assert.False(number.IsFailure); + Assert.IsType>(number); + Assert.Equal(123, number); + + Assert.True(text.IsSuccess); + Assert.False(text.IsFailure); + Assert.IsType>(text); + Assert.Equal("abc", text); + } + + [Fact(DisplayName = "Result.OfAsync with cancellation token should produce expected failure result")] + public async Task ResultOfAsyncWithCancellationTokenShouldProduceExpectedFailureResult() + { + // Given / When + Exception exception = new("failure"); + CancellationToken token = CancellationToken.None; + Result number = await Result.OfAsync(_ => throw exception, token); + Result text = await Result.OfAsync(_ => throw exception, token); + + // Then + Assert.False(number.IsSuccess); + Assert.True(number.IsFailure); + Assert.IsType>(number); + Assert.Equal("failure", (number as Failure)!.Exception.Message); + + Assert.False(text.IsSuccess); + Assert.True(text.IsFailure); + Assert.IsType>(text); + Assert.Equal("failure", (text as Failure)!.Exception.Message); + } + + [Fact(DisplayName = "Result.Success should produce the expected result")] + public void ResultSuccessShouldProduceExpectedResult() + { + // Given / When + Result number = Result.Success(123); + Result text = Result.Success("abc"); + + // Then + Assert.True(number.IsSuccess); + Assert.False(number.IsFailure); + Assert.IsType>(number); + Assert.Equal(123, number); + + Assert.True(text.IsSuccess); + Assert.False(text.IsFailure); + Assert.IsType>(text); + Assert.Equal("abc", text); + } + + [Fact(DisplayName = "Result.Failure should produce the expected result")] + public void ResultFailureShouldProduceExpectedResult() + { + // Given / When + Exception exception = new("failure"); + Result number = Result.Failure(exception); + Result text = Result.Failure(exception); + + // Then + Assert.False(number.IsSuccess); + Assert.True(number.IsFailure); + Assert.IsType>(number); + Assert.Equal("failure", (number as Failure)!.Exception.Message); + + Assert.False(text.IsSuccess); + Assert.True(text.IsFailure); + Assert.IsType>(text); + Assert.Equal("failure", (text as Failure)!.Exception.Message); + } + + [Fact(DisplayName = "Result implicit operator should produce the expected success result.")] + public void ResultImplicitOperatorShouldProduceTheExpectedSuccessResult() + { + // Given / When + Result number = 123; + Result text = "abc"; + + // Then + Assert.True(number.IsSuccess); + Assert.False(number.IsFailure); + Assert.IsType>(number); + Assert.Equal(123, number); + + Assert.True(text.IsSuccess); + Assert.False(text.IsFailure); + Assert.IsType>(text); + Assert.Equal("abc", text); + } + + [Fact(DisplayName = "Result implicit operator should produce the expected failure result.")] + public void ResultImplicitOperatorShouldProduceTheExpectedFailureResult() + { + // Given / When + Exception exception = new("failure"); + Result result = exception; + + // Then + Assert.False(result.IsSuccess); + Assert.True(result.IsFailure); + Assert.IsType>(result); + Assert.Equal("failure", (result as Failure)!.Exception.Message); + } + + [Fact(DisplayName = "Result Success explicit operator should produce the expected result.")] + public void ResultSuccessExplicitOperatorShouldProduceTheExpectedResult() + { + // Given + Result number = Result.Success(123); + Result text = Result.Success("abc"); + + // When + int underlyingNumber = (int)number; + string underlyingText = (string)text; + + // Then + Assert.Equal(123, underlyingNumber); + Assert.Equal("abc", underlyingText); + } + + [Fact(DisplayName = "Result Failure explicit operator should produce the expected result.")] + public void ResultFailureExplicitOperatorShouldProduceTheExpectedResult() + { + // Given + Exception exception = new("failure"); + Result number = Result.Failure(exception); + Result text = Result.Failure(exception); + + // When + Exception numberException = Assert.Throws(() => (int)number); + Exception textException = Assert.Throws(() => (string)text); + + // Then + Assert.Equal("failure", numberException.Message); + Assert.Equal("failure", textException.Message); + } + + [Fact(DisplayName = "Result Success values should be considered equal.")] + public void ResultSuccessValuesShouldBeConsideredEqual() + { + // Given + Success a = Result.Success(123); + Success b = Result.Success(123); + + // When / Then + Assert.Equal(a, b); + Assert.True(a.Equals(b)); + Assert.True(a == b); + Assert.False(a != b); + } + + [Fact(DisplayName = "Result Success values should not be considered equal.")] + public void ResultSuccessValuesShouldNotBeConsideredEqual() + { + // Given + Success a = Result.Success(123); + Success b = Result.Success(456); + + // When / Then + Assert.NotEqual(a, b); + Assert.False(a.Equals(b)); + Assert.True(a != b); + Assert.False(a == b); + } + + [Fact(DisplayName = "Result Failure values should be considered equal.")] + public void ResultFailureValuesShouldBeConsideredEqual() + { + // Given + Exception exception = new("failure"); + Failure a = Result.Failure(exception); + Failure b = Result.Failure(exception); + + // When / Then + Assert.Equal(a, b); + Assert.True(a.Equals(b)); + Assert.True(a == b); + Assert.False(a != b); + + // Note that a and b are equal because they share references to the same exception. + } + + [Fact(DisplayName = "Result Failure values should not be considered equal.")] + public void ResultFailureValuesShouldNotBeConsideredEqual() + { + // Given + Exception exception1 = new("failure a"); + Exception exception2 = new("failure b"); + Failure a = Result.Failure(exception1); + Failure b = Result.Failure(exception2); + + // When / Then + Assert.NotEqual(a, b); + Assert.False(a.Equals(b)); + Assert.True(a != b); + Assert.False(a == b); + } + + [Fact(DisplayName = "Result Success and Failure values should not be considered equal.")] + public void ResultSuccessAndFailureValuesShouldNotBeConsideredEqual() + { + // Given + Exception exception = new("failure"); + Result a = Result.Success(123); + Result b = Result.Failure(exception); + + // When / Then + Assert.NotEqual(a, b); + Assert.False(a.Equals(b)); + Assert.True(a != b); + Assert.False(a == b); + } + + [Fact(DisplayName = "Result Success.GetHashCode should produce the expected result.")] + public void ResultSuccessGetHashCodeShouldProduceExpectedResult() + { + // Given + int expected = 123.GetHashCode(); + Result result = Result.Success(123); + + // When + int actual = result.GetHashCode(); + + // Then + Assert.Equal(expected, actual); + } + + [Fact(DisplayName = "Result Failure.GetHashCode should produce the expected result.")] + public void ResultFailureGetHashCodeShouldProduceExpectedResult() + { + // Given + Exception exception = new("failure"); + int expected = exception.GetHashCode(); + Result result = Result.Failure(exception); + + // When + int actual = result.GetHashCode(); + + // Then + Assert.Equal(expected, actual); + } + + [Fact(DisplayName = "Result Success.GetValueOrDefault should produce the expected result.")] + public void ResultSuccessGetValueOrDefaultShouldProduceExpectedResult() + { + // Given + Result number = Result.Success(123); + Result text = Result.Success("abc"); + + // When + int actualNumber = number.GetValueOrDefault(); + string? actualText = text.GetValueOrDefault(); + + // Then + Assert.Equal(123, actualNumber); + Assert.Equal("abc", actualText); + } + + [Fact(DisplayName = "Result Failure.GetValueOrDefault should produce the expected result.")] + public void ResultFailureGetValueOrDefaultShouldProduceExpectedResult() + { + // Given + Exception exception = new("failure"); + Result number = Result.Failure(exception); + Result text = Result.Failure(exception); + + // When + int actualNumber = number.GetValueOrDefault(); + string? actualText = text.GetValueOrDefault(); + + // Then + Assert.Equal(default, actualNumber); + Assert.Equal(default, actualText); + } + + [Fact(DisplayName = "Result Success.GetValueOrDefault with default value should produce the expected result.")] + public void ResultSuccessGetValueOrDefaultWithDefaultValueShouldProduceExpectedResult() + { + // Given + Result number = Result.Success(123); + Result text = Result.Success("abc"); + + // When + int actualNumber = number.GetValueOrDefault(456); + string? actualText = text.GetValueOrDefault("xyz"); + + // Then + Assert.Equal(123, actualNumber); + Assert.Equal("abc", actualText); + } + + [Fact(DisplayName = "Result Failure.GetValueOrDefault with default value should produce the expected result.")] + public void ResultFailureGetValueOrDefaultWithDefaultValueShouldProduceExpectedResult() + { + // Given + Exception exception = new("failure"); + Result number = Result.Failure(exception); + Result text = Result.Failure(exception); + + // When + int actualNumber = number.GetValueOrDefault(456); + string? actualText = text.GetValueOrDefault("xyz"); + + // Then + Assert.Equal(456, actualNumber); + Assert.Equal("xyz", actualText); + } + + [Fact(DisplayName = "Result Success.GetValueOrThrow should produce the expected result.")] + public void ResultSuccessGetValueOrThrowShouldProduceExpectedResult() + { + // Given + Result number = Result.Success(123); + Result text = Result.Success("abc"); + + // When + int underlyingNumber = number.GetValueOrThrow(); + string underlyingText = text.GetValueOrThrow(); + + // Then + Assert.Equal(123, underlyingNumber); + Assert.Equal("abc", underlyingText); + } + + [Fact(DisplayName = "Result Failure.GetValueOrThrow should produce the expected result.")] + public void ResultFailureGetValueOrThrowShouldProduceExpectedResult() + { + // Given + Exception exception = new("failure"); + Result number = Result.Failure(exception); + Result text = Result.Failure(exception); + + // When + Exception numberException = Assert.Throws(() => number.GetValueOrThrow()); + Exception textException = Assert.Throws(() => text.GetValueOrThrow()); + + // Then + Assert.Equal("failure", numberException.Message); + Assert.Equal("failure", textException.Message); + } + + [Fact(DisplayName = "Result Success.Match should execute the some action.")] + public void ResultSuccessMatchShouldExecuteSuccessAction() + { + // Given + bool successCalled = false; + Result result = 123; + + // When + result.Match(success: _ => { successCalled = true; }); + + // Then + Assert.True(successCalled); + } + + [Fact(DisplayName = "Result Failure.Match should execute the none action.")] + public void ResultFailureMatchShouldExecuteFailureAction() + { + // Given + bool failureCalled = false; + Exception exception = new("failure"); + Result result = Result.Failure(exception); + + // When + result.Match(failure: _ => { failureCalled = true; }); + + // Then + Assert.True(failureCalled); + } + + [Fact(DisplayName = "Result Success.Match should produce the expected result.")] + public void ResultSuccessMatchShouldProduceExpectedResult() + { + // Given + const int expected = 9; + Result result = 3; + + // When + int actual = result.Match( + success: value => value * value, + failure: _ => 0 + ); + + // Then + Assert.Equal(expected, actual); + } + + [Fact(DisplayName = "Result Failure.Match should produce the expected result.")] + public void ResultFailureMatchShouldProduceExpectedResult() + { + // Given + const int expected = 0; + Exception exception = new("failure"); + Result result = Result.Failure(exception); + + // When + int actual = result.Match( + success: value => value * value, + failure: _ => 0 + ); + + // Then + Assert.Equal(expected, actual); + } + + [Fact(DisplayName = "Result Success.Select should produce the expected result")] + public void ResultSuccessSelectShouldProduceExpectedResult() + { + // Given + Result expected = Result.Success(); + Result result = 123; + + // When + Result actual = result.Select(_ => { }); + + // Then + Assert.Equal(expected, actual); + } + + [Fact(DisplayName = "Result Failure.Select should produce the expected result")] + public void ResultFailureSelectShouldProduceExpectedResult() + { + // Given + Exception exception = new("Failure"); + Result expected = Result.Failure(exception); + Result result = Result.Failure(exception); + + // When + Result actual = result.Select(_ => { }); + + // Then + Assert.Equal(expected, actual); + } + + [Fact(DisplayName = "Result Success.Select should produce the expected result")] + public void ResultSuccessSelectTResultShouldProduceExpectedResult() + { + // Given + Result expected = 9; + Result result = 3; + + // When + Result actual = result.Select(x => x * x); + + // Then + Assert.Equal(expected, actual); + } + + [Fact(DisplayName = "Result Failure.Select should produce the expected result")] + public void ResultFailureSelectTResultShouldProduceExpectedResult() + { + // Given + Exception exception = new("failure"); + Result result = Result.Failure(exception); + + // When + Result actual = result.Select(x => x * x); + + // Then + Assert.Equal(Result.Failure(exception), actual); + } + + [Fact(DisplayName = "Result Success.SelectMany should produce the expected result")] + public void ResultSuccessSelectManyShouldProduceExpectedResult() + { + // Given + Result expected = Result.Success(); + Result result = 3; + + // When + Result actual = result.SelectMany(_ => Result.Success()); + + // Then + Assert.Equal(expected, actual); + } + + [Fact(DisplayName = "Result Failure.SelectMany should produce the expected result")] + public void ResultFailureSelectManyShouldProduceExpectedResult() + { + // Given + Exception exception = new("Failure"); + Result expected = Result.Failure(exception); + Result result = Result.Failure(exception); + + // When + Result actual = result.SelectMany(_ => Result.Success()); + + // Then + Assert.Equal(expected, actual); + } + + [Fact(DisplayName = "Result Success.SelectMany should produce the expected result")] + public void ResultSuccessSelectManyTResultShouldProduceExpectedResult() + { + // Given + Result expected = 9; + Result result = 3; + + // When + Result actual = result.SelectMany(x => Result.Success(x * x)); + + // Then + Assert.Equal(expected, actual); + } + + [Fact(DisplayName = "Result Failure.SelectMany should produce the expected result")] + public void ResultFailureSelectManyTResultShouldProduceExpectedResult() + { + // Given + Exception exception = new("failure"); + Result result = Result.Failure(exception); + + // When + Result actual = result.SelectMany(x => Result.Success(x * x)); + + // Then + Assert.Equal(Result.Failure(exception), actual); + } + + [Fact(DisplayName = "Result Success.Throw should do nothing")] + public void ResultSuccessThrowShouldDoNothing() + { + // Given + Result result = Result.Success(123); + + // When / Then + result.Throw(); + } + + [Fact(DisplayName = "Result Failure.Throw should throw Exception")] + public void ResultFailureThrowShouldThrowException() + { + // Given + Exception exception = new("failure"); + Result result = Result.Failure(exception); + + // When / Then + Assert.Throws(() => result.Throw()); + } + + + [Fact(DisplayName = "Result Success.ToString should produce the expected result.")] + public void ResultSuccessToStringShouldProduceExpectedResult() + { + // Given + Result number = 123; + Result text = "abc"; + + // When + string numberString = number.ToString(); + string textString = text.ToString(); + + // Then + Assert.Equal("123", numberString); + Assert.Equal("abc", textString); + } + + [Fact(DisplayName = "Result Failure.ToString should produce the expected result.")] + public void ResultFailureToStringShouldProduceExpectedResult() + { + // Given + Exception exception = new("failure"); + Result number = Result.Failure(exception); + Result text = Result.Failure(exception); + + // When + string numberString = number.ToString(); + string textString = text.ToString(); + + // Then + Assert.Equal("System.Exception: failure", numberString); + Assert.Equal("System.Exception: failure", textString); + } + + [Fact(DisplayName = "Result Success.Dispose should dispose of the underlying value.")] + public void ResultSuccessDisposeShouldDisposeUnderlyingValue() + { + // Given + Disposable disposable = new(); + Success result = Result.Success(disposable); + + // When + result.Dispose(); + + // Then + Assert.True(disposable.IsDisposed); + } + + [Fact(DisplayName = "Result Failure.Dispose should do nothing.")] + public void ResultFailureDisposeShouldDoNothing() + { + // Given + Disposable disposable = new(); + Exception exception = new("failure"); + Failure result = Result.Failure(exception); + + // When + result.Dispose(); + + // Then + Assert.False(disposable.IsDisposed); + } + + [Fact(DisplayName = "Result Success.DisposeAsync should dispose of the underlying value.")] + public async Task ResultSuccessDisposeAsyncShouldDisposeUnderlyingValue() + { + // Given + Disposable disposable = new(); + Success result = Result.Success(disposable); + + // When + await result.DisposeAsync(); + + // Then + Assert.True(disposable.IsDisposed); + } + + [Fact(DisplayName = "Result Failure.DisposeAsync should do nothing.")] + public async Task ResultFailureDisposeAsyncShouldDoNothing() + { + // Given + Disposable disposable = new(); + Exception exception = new("failure"); + Failure result = Result.Failure(exception); + + // When + await result.DisposeAsync(); + + // Then + Assert.False(disposable.IsDisposed); + } +} diff --git a/OnixLabs.Core.UnitTests/ResultNonGenericTests.cs b/OnixLabs.Core.UnitTests/ResultNonGenericTests.cs deleted file mode 100644 index b6e25e5..0000000 --- a/OnixLabs.Core.UnitTests/ResultNonGenericTests.cs +++ /dev/null @@ -1,371 +0,0 @@ -// Copyright 2020 ONIXLabs -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -using System; -using Xunit; - -namespace OnixLabs.Core.UnitTests; - -public sealed class ResultNonGenericTests -{ - [Fact(DisplayName = "Result.Of should produce expected success result")] - public void ResultOfShouldProduceExpectedSuccessResult() - { - // Given / When - Result result = Result.Of(() => { }); - - // Then - Assert.True(result.IsSuccess); - Assert.False(result.IsFailure); - Assert.IsType(result); - } - - [Fact(DisplayName = "Result.Of should produce expected failure result")] - public void ResultOfShouldProduceExpectedFailureResult() - { - // Given / When - Exception exception = new("failure"); - Result result = Result.Of(() => throw exception); - - // Then - Assert.False(result.IsSuccess); - Assert.True(result.IsFailure); - Assert.IsType(result); - } - - [Fact(DisplayName = "Result.Success should produce the expected result")] - public void ResultSuccessShouldProduceExpectedResult() - { - // Given / When - Result result = Result.Success(); - - // Then - Assert.True(result.IsSuccess); - Assert.False(result.IsFailure); - Assert.IsType(result); - } - - [Fact(DisplayName = "Result.Failure should produce the expected result")] - public void ResultFailureShouldProduceExpectedResult() - { - // Given / When - Exception exception = new("failure"); - Result result = Result.Failure(exception); - - // Then - Assert.False(result.IsSuccess); - Assert.True(result.IsFailure); - Assert.IsType(result); - } - - [Fact(DisplayName = "Result implicit operator should produce the expected failure result.")] - public void ResultImplicitOperatorShouldProduceTheExpectedFailureResult() - { - // Given / When - Exception exception = new("failure"); - Result result = exception; - - // Then - Assert.False(result.IsSuccess); - Assert.True(result.IsFailure); - Assert.IsType(result); - Assert.Equal("failure", (result as Failure)!.Exception.Message); - } - - [Fact(DisplayName = "Result Success values should be considered equal.")] - public void ResultSuccessValuesShouldBeConsideredEqual() - { - // Given - Result a = Result.Success(); - Result b = Success.Instance; - - // When / Then - Assert.Equal(a, b); - Assert.True(a == b); - Assert.True(a.Equals(b)); - } - - [Fact(DisplayName = "Result Failure values should be considered equal.")] - public void ResultFailureValuesShouldBeConsideredEqual() - { - // Given - Exception exception = new("failure"); - Result a = Result.Failure(exception); - Result b = exception; - - // When / Then - Assert.Equal(a, b); - Assert.True(a == b); - Assert.True(a.Equals(b)); - - // Note that a and b are equal because they share references to the same exception. - } - - [Fact(DisplayName = "Result Failure values should not be considered equal.")] - public void ResultFailureValuesShouldNotBeConsideredEqual() - { - // Given - Exception exception1 = new("failure a"); - Exception exception2 = new("failure b"); - Result a = Result.Failure(exception1); - Result b = Result.Failure(exception2); - - // When / Then - Assert.NotEqual(a, b); - Assert.True(a != b); - Assert.False(a.Equals(b)); - } - - [Fact(DisplayName = "Result Success and Failure values should not be considered equal.")] - public void ResultSuccessAndFailureValuesShouldNotBeConsideredEqual() - { - // Given - Exception exception = new("failure"); - Result a = Result.Success(); - Result b = Result.Failure(exception); - - // When / Then - Assert.NotEqual(a, b); - Assert.True(a != b); - Assert.False(a.Equals(b)); - } - - [Fact(DisplayName = "Result Success.GetHashCode should produce the expected result.")] - public void ResultSuccessGetHashCodeShouldProduceExpectedResult() - { - // Given - const int expected = 0; - Result result = Result.Success(); - - // When - int actual = result.GetHashCode(); - - // Then - Assert.Equal(expected, actual); - } - - [Fact(DisplayName = "Result Failure.GetHashCode should produce the expected result.")] - public void ResultFailureGetHashCodeShouldProduceExpectedResult() - { - // Given - Exception exception = new("failure"); - int expected = exception.GetHashCode(); - Result result = Result.Failure(exception); - - // When - int actual = result.GetHashCode(); - - // Then - Assert.Equal(expected, actual); - } - - [Fact(DisplayName = "Result Success.Match should execute the some action.")] - public void ResultSuccessMatchShouldExecuteSuccessAction() - { - // Given - bool someCalled = false; - Result result = Result.Success(); - - // When - result.Match(success: () => { someCalled = true; }); - - // Then - Assert.True(someCalled); - } - - [Fact(DisplayName = "Result Failure.Match should execute the none action.")] - public void ResultFailureMatchShouldExecuteFailureAction() - { - // Given - bool noneCalled = false; - Exception exception = new("failure"); - Result result = Result.Failure(exception); - - // When - result.Match(failure: _ => { noneCalled = true; }); - - // Then - Assert.True(noneCalled); - } - - [Fact(DisplayName = "Result Success.Match should produce the expected result.")] - public void ResultSuccessMatchShouldProduceExpectedResult() - { - // Given - const int expected = 9; - Result result = Result.Success(); - - // When - int actual = result.Match( - success: () => 9, - failure: _ => 0 - ); - - // Then - Assert.Equal(expected, actual); - } - - [Fact(DisplayName = "Result Failure.Match should produce the expected result.")] - public void ResultFailureMatchShouldProduceExpectedResult() - { - // Given - const int expected = 0; - Exception exception = new("failure"); - Result result = Result.Failure(exception); - - // When - int actual = result.Match( - success: () => 9, - failure: _ => 0 - ); - - // Then - Assert.Equal(expected, actual); - } - - [Fact(DisplayName = "Result Success.Select should produce the expected result")] - public void ResultSuccessSelectShouldProduceExpectedResult() - { - // Given - Result expected = Result.Success(); - - // When - Result actual = expected.Select(() => { }); - - // Then - Assert.Equal(expected, actual); - } - - [Fact(DisplayName = "Result Failure.Select should produce the expected result")] - public void ResultFailureSelectShouldProduceExpectedResult() - { - // Given - Result expected = Result.Failure(new Exception("Failure")); - - // When - Result actual = expected.Select(() => { }); - - // Then - Assert.Equal(expected, actual); - } - - [Fact(DisplayName = "Result Success.Select should produce the expected result")] - public void ResultSuccessSelectTResultShouldProduceExpectedResult() - { - // Given - const int expected = 9; - Result result = Result.Success(); - - // When - Result actual = result.Select(() => 9); - - // Then - Assert.Equal(expected, actual); - } - - [Fact(DisplayName = "Result Failure.Select should produce the expected result")] - public void ResultFailureSelectTResultShouldProduceExpectedResult() - { - // Given - Exception exception = new("failure"); - Result result = Result.Failure(exception); - - // When - Result actual = result.Select(() => 9); - - // Then - Assert.Equal(Result.Failure(exception), actual); - } - - [Fact(DisplayName = "Result Success.SelectMany should produce the expected result")] - public void ResultSuccessSelectManyShouldProduceExpectedResult() - { - // Given - Result expected = Result.Success(); - - // When - Result actual = expected.SelectMany(Result.Success); - - // Then - Assert.Equal(expected, actual); - } - - [Fact(DisplayName = "Result Failure.SelectMany should produce the expected result")] - public void ResultFailureSelectManyShouldProduceExpectedResult() - { - // Given - Result expected = Result.Failure(new Exception("Failure")); - - // When - Result actual = expected.SelectMany(Result.Success); - - // Then - Assert.Equal(expected, actual); - } - - [Fact(DisplayName = "Result Success.SelectMany should produce the expected result")] - public void ResultSuccessSelectTResultManyShouldProduceExpectedResult() - { - // Given - const int expected = 9; - Result result = Result.Success(); - - // When - Result actual = result.SelectMany(() => 9); - - // Then - Assert.Equal(expected, actual); - } - - [Fact(DisplayName = "Result Failure.SelectMany should produce the expected result")] - public void ResultFailureSelectTResultManyShouldProduceExpectedResult() - { - // Given - Exception exception = new("failure"); - Result result = Result.Failure(exception); - - // When - Result actual = result.SelectMany(() => 9); - - // Then - Assert.Equal(Result.Failure(exception), actual); - } - - [Fact(DisplayName = "Result Success.ToString should produce the expected result.")] - public void ResultSuccessToStringShouldProduceExpectedResult() - { - // Given - Result result = Result.Success(); - - // When - string resultString = result.ToString(); - - // Then - Assert.Equal(string.Empty, resultString); - } - - [Fact(DisplayName = "Result Failure.ToString should produce the expected result.")] - public void ResultFailureToStringShouldProduceExpectedResult() - { - // Given - Exception exception = new("failure"); - Result result = Result.Failure(exception); - - // When - string resultString = result.ToString(); - - // Then - Assert.Equal("System.Exception: failure", resultString); - } -} diff --git a/OnixLabs.Core.UnitTests/ResultTests.cs b/OnixLabs.Core.UnitTests/ResultTests.cs index 22c8f15..0c9bca1 100644 --- a/OnixLabs.Core.UnitTests/ResultTests.cs +++ b/OnixLabs.Core.UnitTests/ResultTests.cs @@ -13,6 +13,8 @@ // limitations under the License. using System; +using System.Threading; +using System.Threading.Tasks; using Xunit; namespace OnixLabs.Core.UnitTests; @@ -23,19 +25,12 @@ public sealed class ResultTests public void ResultOfShouldProduceExpectedSuccessResult() { // Given / When - Result number = Result.Of(() => 123); - Result text = Result.Of(() => "abc"); + Result result = Result.Of(() => { }); // Then - Assert.True(number.IsSuccess); - Assert.False(number.IsFailure); - Assert.IsType>(number); - Assert.Equal(123, number); - - Assert.True(text.IsSuccess); - Assert.False(text.IsFailure); - Assert.IsType>(text); - Assert.Equal("abc", text); + Assert.True(result.IsSuccess); + Assert.False(result.IsFailure); + Assert.IsType(result); } [Fact(DisplayName = "Result.Of should produce expected failure result")] @@ -43,150 +38,119 @@ public void ResultOfShouldProduceExpectedFailureResult() { // Given / When Exception exception = new("failure"); - Result number = Result.Of(() => throw exception); - Result text = Result.Of(() => throw exception); + Result result = Result.Of(() => throw exception); // Then - Assert.False(number.IsSuccess); - Assert.True(number.IsFailure); - Assert.IsType>(number); - Assert.Equal("failure", (number as Failure)!.Exception.Message); - - Assert.False(text.IsSuccess); - Assert.True(text.IsFailure); - Assert.IsType>(text); - Assert.Equal("failure", (text as Failure)!.Exception.Message); + Assert.False(result.IsSuccess); + Assert.True(result.IsFailure); + Assert.IsType(result); } - [Fact(DisplayName = "Result.Success should produce the expected result")] - public void ResultSuccessShouldProduceExpectedResult() + [Fact(DisplayName = "Result.OfAsync should produce expected success result")] + public async Task ResultOfAsyncShouldProduceExpectedSuccessResult() { // Given / When - Result number = Result.Success(123); - Result text = Result.Success("abc"); + Result result = await Result.OfAsync(async () => await Task.CompletedTask); // Then - Assert.True(number.IsSuccess); - Assert.False(number.IsFailure); - Assert.IsType>(number); - Assert.Equal(123, number); - - Assert.True(text.IsSuccess); - Assert.False(text.IsFailure); - Assert.IsType>(text); - Assert.Equal("abc", text); + Assert.True(result.IsSuccess); + Assert.False(result.IsFailure); + Assert.IsType(result); } - [Fact(DisplayName = "Result.Failure should produce the expected result")] - public void ResultFailureShouldProduceExpectedResult() + [Fact(DisplayName = "Result.OfAsync should produce expected failure result")] + public async Task ResultOfAsyncShouldProduceExpectedFailureResult() { // Given / When Exception exception = new("failure"); - Result number = Result.Failure(exception); - Result text = Result.Failure(exception); + Result result = await Result.OfAsync(() => throw exception); // Then - Assert.False(number.IsSuccess); - Assert.True(number.IsFailure); - Assert.IsType>(number); - Assert.Equal("failure", (number as Failure)!.Exception.Message); - - Assert.False(text.IsSuccess); - Assert.True(text.IsFailure); - Assert.IsType>(text); - Assert.Equal("failure", (text as Failure)!.Exception.Message); + Assert.False(result.IsSuccess); + Assert.True(result.IsFailure); + Assert.IsType(result); } - [Fact(DisplayName = "Result implicit operator should produce the expected success result.")] - public void ResultImplicitOperatorShouldProduceTheExpectedSuccessResult() + [Fact(DisplayName = "Result.OfAsync with cancellation token should produce expected success result")] + public async Task ResultOfAsyncWithCancellationTokenShouldProduceExpectedSuccessResult() { // Given / When - Result number = 123; - Result text = "abc"; + CancellationToken token = CancellationToken.None; + Result result = await Result.OfAsync(async _ => await Task.CompletedTask, token); // Then - Assert.True(number.IsSuccess); - Assert.False(number.IsFailure); - Assert.IsType>(number); - Assert.Equal(123, number); - - Assert.True(text.IsSuccess); - Assert.False(text.IsFailure); - Assert.IsType>(text); - Assert.Equal("abc", text); + Assert.True(result.IsSuccess); + Assert.False(result.IsFailure); + Assert.IsType(result); } - [Fact(DisplayName = "Result implicit operator should produce the expected failure result.")] - public void ResultImplicitOperatorShouldProduceTheExpectedFailureResult() + [Fact(DisplayName = "Result.OfAsync with cancellation token should produce expected failure result")] + public async Task ResultOfAsyncWithCancellationTokenShouldProduceExpectedFailureResult() { // Given / When Exception exception = new("failure"); - Result result = exception; + CancellationToken token = CancellationToken.None; + Result result = await Result.OfAsync(_ => throw exception, token); // Then Assert.False(result.IsSuccess); Assert.True(result.IsFailure); - Assert.IsType>(result); - Assert.Equal("failure", (result as Failure)!.Exception.Message); + Assert.IsType(result); } - [Fact(DisplayName = "Result Success explicit operator should produce the expected result.")] - public void ResultSuccessExplicitOperatorShouldProduceTheExpectedResult() + [Fact(DisplayName = "Result.Success should produce the expected result")] + public void ResultSuccessShouldProduceExpectedResult() { - // Given - Result number = Result.Success(123); - Result text = Result.Success("abc"); - - // When - int underlyingNumber = (int)number; - string underlyingText = (string)text; + // Given / When + Result result = Result.Success(); // Then - Assert.Equal(123, underlyingNumber); - Assert.Equal("abc", underlyingText); + Assert.True(result.IsSuccess); + Assert.False(result.IsFailure); + Assert.IsType(result); } - [Fact(DisplayName = "Result Failure explicit operator should produce the expected result.")] - public void ResultFailureExplicitOperatorShouldProduceTheExpectedResult() + [Fact(DisplayName = "Result.Failure should produce the expected result")] + public void ResultFailureShouldProduceExpectedResult() { - // Given + // Given / When Exception exception = new("failure"); - Result number = Result.Failure(exception); - Result text = Result.Failure(exception); + Result result = Result.Failure(exception); - // When - Exception numberException = Assert.Throws(() => (int)number); - Exception textException = Assert.Throws(() => (string)text); + // Then + Assert.False(result.IsSuccess); + Assert.True(result.IsFailure); + Assert.IsType(result); + } + + [Fact(DisplayName = "Result implicit operator should produce the expected failure result.")] + public void ResultImplicitOperatorShouldProduceTheExpectedFailureResult() + { + // Given / When + Exception exception = new("failure"); + Result result = exception; // Then - Assert.Equal("failure", numberException.Message); - Assert.Equal("failure", textException.Message); + Assert.False(result.IsSuccess); + Assert.True(result.IsFailure); + Assert.IsType(result); + Assert.Equal("failure", (result as Failure)!.Exception.Message); } [Fact(DisplayName = "Result Success values should be considered equal.")] public void ResultSuccessValuesShouldBeConsideredEqual() { // Given - Result a = Result.Success(123); - Result b = Result.Success(123); + Success a = Result.Success(); + Success b = Success.Instance; // When / Then Assert.Equal(a, b); - Assert.True(a == b); Assert.True(a.Equals(b)); - } - - [Fact(DisplayName = "Result Success values should not be considered equal.")] - public void ResultSuccessValuesShouldNotBeConsideredEqual() - { - // Given - Result a = Result.Success(123); - Result b = Result.Success(456); - - // When / Then - Assert.NotEqual(a, b); - Assert.True(a != b); - Assert.False(a.Equals(b)); + Assert.True(a == b); + Assert.False(a != b); + Assert.True((Result)a == (Result)b); + Assert.False((Result)a != (Result)b); } [Fact(DisplayName = "Result Failure values should be considered equal.")] @@ -194,13 +158,16 @@ public void ResultFailureValuesShouldBeConsideredEqual() { // Given Exception exception = new("failure"); - Result a = Result.Failure(exception); - Result b = Result.Failure(exception); + Failure a = Result.Failure(exception); + Failure b = exception; // When / Then Assert.Equal(a, b); - Assert.True(a == b); Assert.True(a.Equals(b)); + Assert.True(a == b); + Assert.False(a != b); + Assert.True((Result)a == (Result)b); + Assert.False((Result)a != (Result)b); // Note that a and b are equal because they share references to the same exception. } @@ -211,8 +178,8 @@ public void ResultFailureValuesShouldNotBeConsideredEqual() // Given Exception exception1 = new("failure a"); Exception exception2 = new("failure b"); - Result a = Result.Failure(exception1); - Result b = Result.Failure(exception2); + Result a = Result.Failure(exception1); + Result b = Result.Failure(exception2); // When / Then Assert.NotEqual(a, b); @@ -225,8 +192,8 @@ public void ResultSuccessAndFailureValuesShouldNotBeConsideredEqual() { // Given Exception exception = new("failure"); - Result a = Result.Success(123); - Result b = Result.Failure(exception); + Result a = Result.Success(); + Result b = Result.Failure(exception); // When / Then Assert.NotEqual(a, b); @@ -238,8 +205,8 @@ public void ResultSuccessAndFailureValuesShouldNotBeConsideredEqual() public void ResultSuccessGetHashCodeShouldProduceExpectedResult() { // Given - int expected = 123.GetHashCode(); - Result result = Result.Success(123); + const int expected = 0; + Result result = Result.Success(); // When int actual = result.GetHashCode(); @@ -254,7 +221,7 @@ public void ResultFailureGetHashCodeShouldProduceExpectedResult() // Given Exception exception = new("failure"); int expected = exception.GetHashCode(); - Result result = Result.Failure(exception); + Result result = Result.Failure(exception); // When int actual = result.GetHashCode(); @@ -263,132 +230,33 @@ public void ResultFailureGetHashCodeShouldProduceExpectedResult() Assert.Equal(expected, actual); } - [Fact(DisplayName = "Result Success.GetValueOrDefault should produce the expected result.")] - public void ResultSuccessGetValueOrDefaultShouldProduceExpectedResult() - { - // Given - Result number = Result.Success(123); - Result text = Result.Success("abc"); - - // When - int actualNumber = number.GetValueOrDefault(); - string? actualText = text.GetValueOrDefault(); - - // Then - Assert.Equal(123, actualNumber); - Assert.Equal("abc", actualText); - } - - [Fact(DisplayName = "Result Failure.GetValueOrDefault should produce the expected result.")] - public void ResultFailureGetValueOrDefaultShouldProduceExpectedResult() - { - // Given - Exception exception = new("failure"); - Result number = Result.Failure(exception); - Result text = Result.Failure(exception); - - // When - int actualNumber = number.GetValueOrDefault(); - string? actualText = text.GetValueOrDefault(); - - // Then - Assert.Equal(default, actualNumber); - Assert.Equal(default, actualText); - } - - [Fact(DisplayName = "Result Success.GetValueOrDefault with default value should produce the expected result.")] - public void ResultSuccessGetValueOrDefaultWithDefaultValueShouldProduceExpectedResult() - { - // Given - Result number = Result.Success(123); - Result text = Result.Success("abc"); - - // When - int actualNumber = number.GetValueOrDefault(456); - string? actualText = text.GetValueOrDefault("xyz"); - - // Then - Assert.Equal(123, actualNumber); - Assert.Equal("abc", actualText); - } - - [Fact(DisplayName = "Result Failure.GetValueOrDefault with default value should produce the expected result.")] - public void ResultFailureGetValueOrDefaultWithDefaultValueShouldProduceExpectedResult() - { - // Given - Exception exception = new("failure"); - Result number = Result.Failure(exception); - Result text = Result.Failure(exception); - - // When - int actualNumber = number.GetValueOrDefault(456); - string? actualText = text.GetValueOrDefault("xyz"); - - // Then - Assert.Equal(456, actualNumber); - Assert.Equal("xyz", actualText); - } - - [Fact(DisplayName = "Result Success.GetValueOrThrow should produce the expected result.")] - public void ResultSuccessGetValueOrThrowShouldProduceExpectedResult() - { - // Given - Result number = Result.Success(123); - Result text = Result.Success("abc"); - - // When - int underlyingNumber = number.GetValueOrThrow(); - string underlyingText = text.GetValueOrThrow(); - - // Then - Assert.Equal(123, underlyingNumber); - Assert.Equal("abc", underlyingText); - } - - [Fact(DisplayName = "Result Failure.GetValueOrThrow should produce the expected result.")] - public void ResultFailureGetValueOrThrowShouldProduceExpectedResult() - { - // Given - Exception exception = new("failure"); - Result number = Result.Failure(exception); - Result text = Result.Failure(exception); - - // When - Exception numberException = Assert.Throws(() => number.GetValueOrThrow()); - Exception textException = Assert.Throws(() => text.GetValueOrThrow()); - - // Then - Assert.Equal("failure", numberException.Message); - Assert.Equal("failure", textException.Message); - } - [Fact(DisplayName = "Result Success.Match should execute the some action.")] public void ResultSuccessMatchShouldExecuteSuccessAction() { // Given - bool someCalled = false; - Result result = 123; + bool successCalled = false; + Result result = Result.Success(); // When - result.Match(success: _ => { someCalled = true; }); + result.Match(success: () => { successCalled = true; }); // Then - Assert.True(someCalled); + Assert.True(successCalled); } [Fact(DisplayName = "Result Failure.Match should execute the none action.")] public void ResultFailureMatchShouldExecuteFailureAction() { // Given - bool noneCalled = false; + bool failureCalled = false; Exception exception = new("failure"); - Result result = Result.Failure(exception); + Result result = Result.Failure(exception); // When - result.Match(failure: _ => { noneCalled = true; }); + result.Match(failure: _ => { failureCalled = true; }); // Then - Assert.True(noneCalled); + Assert.True(failureCalled); } [Fact(DisplayName = "Result Success.Match should produce the expected result.")] @@ -396,11 +264,11 @@ public void ResultSuccessMatchShouldProduceExpectedResult() { // Given const int expected = 9; - Result result = 3; + Result result = Result.Success(); // When int actual = result.Match( - success: value => value * value, + success: () => 9, failure: _ => 0 ); @@ -414,11 +282,11 @@ public void ResultFailureMatchShouldProduceExpectedResult() // Given const int expected = 0; Exception exception = new("failure"); - Result result = Result.Failure(exception); + Result result = Result.Failure(exception); // When int actual = result.Match( - success: value => value * value, + success: () => 9, failure: _ => 0 ); @@ -431,10 +299,9 @@ public void ResultSuccessSelectShouldProduceExpectedResult() { // Given Result expected = Result.Success(); - Result result = 123; // When - Result actual = result.Select(_ => { }); + Result actual = expected.Select(() => { }); // Then Assert.Equal(expected, actual); @@ -444,12 +311,10 @@ public void ResultSuccessSelectShouldProduceExpectedResult() public void ResultFailureSelectShouldProduceExpectedResult() { // Given - Exception exception = new("Failure"); - Result expected = Result.Failure(exception); - Result result = Result.Failure(exception); + Result expected = Result.Failure(new Exception("Failure")); // When - Result actual = result.Select(_ => { }); + Result actual = expected.Select(() => { }); // Then Assert.Equal(expected, actual); @@ -459,11 +324,11 @@ public void ResultFailureSelectShouldProduceExpectedResult() public void ResultSuccessSelectTResultShouldProduceExpectedResult() { // Given - Result expected = 9; - Result result = 3; + const int expected = 9; + Result result = Result.Success(); // When - Result actual = result.Select(x => x * x); + Result actual = result.Select(() => 9); // Then Assert.Equal(expected, actual); @@ -474,10 +339,10 @@ public void ResultFailureSelectTResultShouldProduceExpectedResult() { // Given Exception exception = new("failure"); - Result result = Result.Failure(exception); + Result result = Result.Failure(exception); // When - Result actual = result.Select(x => x * x); + Result actual = result.Select(() => 9); // Then Assert.Equal(Result.Failure(exception), actual); @@ -488,10 +353,9 @@ public void ResultSuccessSelectManyShouldProduceExpectedResult() { // Given Result expected = Result.Success(); - Result result = 3; // When - Result actual = result.SelectMany(_ => Result.Success()); + Result actual = expected.SelectMany(Result.Success); // Then Assert.Equal(expected, actual); @@ -501,59 +365,75 @@ public void ResultSuccessSelectManyShouldProduceExpectedResult() public void ResultFailureSelectManyShouldProduceExpectedResult() { // Given - Exception exception = new("Failure"); - Result expected = Result.Failure(exception); - Result result = Result.Failure(exception); + Result expected = Result.Failure(new Exception("Failure")); // When - Result actual = result.SelectMany(_ => Result.Success()); + Result actual = expected.SelectMany(Result.Success); // Then Assert.Equal(expected, actual); } [Fact(DisplayName = "Result Success.SelectMany should produce the expected result")] - public void ResultSuccessSelectTResultManyShouldProduceExpectedResult() + public void ResultSuccessSelectManyTResultShouldProduceExpectedResult() { // Given - Result expected = 9; - Result result = 3; + const int expected = 9; + Result result = Result.Success(); // When - Result actual = result.SelectMany(x => Result.Success(x * x)); + Result actual = result.SelectMany(() => 9); // Then Assert.Equal(expected, actual); } [Fact(DisplayName = "Result Failure.SelectMany should produce the expected result")] - public void ResultFailureSelectTResultManyShouldProduceExpectedResult() + public void ResultFailureSelectManyTResultShouldProduceExpectedResult() { // Given Exception exception = new("failure"); - Result result = Result.Failure(exception); + Result result = Result.Failure(exception); // When - Result actual = result.SelectMany(x => Result.Success(x * x)); + Result actual = result.SelectMany(() => 9); // Then Assert.Equal(Result.Failure(exception), actual); } + [Fact(DisplayName = "Result Success.Throw should do nothing")] + public void ResultSuccessThrowShouldDoNothing() + { + // Given + Result result = Result.Success(); + + // When / Then + result.Throw(); + } + + [Fact(DisplayName = "Result Failure.Throw should throw Exception")] + public void ResultFailureThrowShouldThrowException() + { + // Given + Exception exception = new("failure"); + Result result = Result.Failure(exception); + + // When / Then + Assert.Throws(() => result.Throw()); + } + [Fact(DisplayName = "Result Success.ToString should produce the expected result.")] public void ResultSuccessToStringShouldProduceExpectedResult() { // Given - Result number = 123; - Result text = "abc"; + Result result = Result.Success(); // When - string numberString = number.ToString(); - string textString = text.ToString(); + string resultString = result.ToString(); // Then - Assert.Equal("123", numberString); - Assert.Equal("abc", textString); + Assert.Equal(string.Empty, resultString); } [Fact(DisplayName = "Result Failure.ToString should produce the expected result.")] @@ -561,15 +441,12 @@ public void ResultFailureToStringShouldProduceExpectedResult() { // Given Exception exception = new("failure"); - Result number = Result.Failure(exception); - Result text = Result.Failure(exception); + Result result = Result.Failure(exception); // When - string numberString = number.ToString(); - string textString = text.ToString(); + string resultString = result.ToString(); // Then - Assert.Equal("System.Exception: failure", numberString); - Assert.Equal("System.Exception: failure", textString); + Assert.Equal("System.Exception: failure", resultString); } } diff --git a/OnixLabs.Core.UnitTests/StringExtensionTests.cs b/OnixLabs.Core.UnitTests/StringExtensionTests.cs index 49d02c1..ce2fdbb 100644 --- a/OnixLabs.Core.UnitTests/StringExtensionTests.cs +++ b/OnixLabs.Core.UnitTests/StringExtensionTests.cs @@ -188,18 +188,6 @@ public void ToTimeOnlyShouldProduceExpectedResult(string value, int hour, int mi Assert.Equal(second, actual.Second); } - [Theory(DisplayName = "String.Wrap should wrap the current string value between the before and after string values")] - [InlineData("<", "value", ">", "")] - [InlineData("BEFORE:", "value", ":AFTER", "BEFORE:value:AFTER")] - public void WrapShouldProduceExpectedResult(string before, string value, string after, string expected) - { - // When - string actual = value.Wrap(before, after); - - // Then - Assert.Equal(expected, actual); - } - [Theory(DisplayName = "String.ToEscapedString should produce the expected result")] [InlineData("\n", @"\n")] [InlineData("\r", @"\r")] @@ -207,6 +195,43 @@ public void WrapShouldProduceExpectedResult(string before, string value, string [InlineData("\"", @"\""")] [InlineData("\'", @"\'")] [InlineData("\\", @"\\")] + [InlineData("\u0000", @"\u0000")] + [InlineData("\u0001", @"\u0001")] + [InlineData("\u0002", @"\u0002")] + [InlineData("\u0003", @"\u0003")] + [InlineData("\u0004", @"\u0004")] + [InlineData("\u0005", @"\u0005")] + [InlineData("\u0006", @"\u0006")] + [InlineData("\u0007", @"\u0007")] + [InlineData("\u0008", @"\u0008")] + [InlineData("\u000B", @"\u000B")] + [InlineData("\u000C", @"\u000C")] + [InlineData("\u000E", @"\u000E")] + [InlineData("\u000F", @"\u000F")] + [InlineData("\u0010", @"\u0010")] + [InlineData("\u0011", @"\u0011")] + [InlineData("\u0012", @"\u0012")] + [InlineData("\u0013", @"\u0013")] + [InlineData("\u0014", @"\u0014")] + [InlineData("\u0015", @"\u0015")] + [InlineData("\u0016", @"\u0016")] + [InlineData("\u0017", @"\u0017")] + [InlineData("\u0018", @"\u0018")] + [InlineData("\u0019", @"\u0019")] + [InlineData("\u001A", @"\u001A")] + [InlineData("\u001B", @"\u001B")] + [InlineData("\u001C", @"\u001C")] + [InlineData("\u001D", @"\u001D")] + [InlineData("\u001E", @"\u001E")] + [InlineData("\u001F", @"\u001F")] + [InlineData("\u007F", @"\u007F")] + [InlineData("\u0085", @"\u0085")] + [InlineData("\u0098", @"\u0098")] + [InlineData("\u009C", @"\u009C")] + [InlineData("\u009D", @"\u009D")] + [InlineData("\u009E", @"\u009E")] + [InlineData("\u009F", @"\u009F")] + [InlineData("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789", "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789")] public void ToEscapedStringShouldProduceExpectedResult(string value, string expected) { // When @@ -215,4 +240,48 @@ public void ToEscapedStringShouldProduceExpectedResult(string value, string expe // Then Assert.Equal(expected, actual); } + + [Fact(DisplayName = "String.TryCopyTo should return true if the string is successfully copied to the target span")] + public void StringTryCopyToShouldReturnTrueIfStringIsSuccessfullyCopiedToTargetSpan() + { + // Given + const string expected = "Hello, World!"; + int expectedCharsWritten = 13; + Span destination = stackalloc char[expectedCharsWritten]; + + // When + bool result = expected.TryCopyTo(destination, out expectedCharsWritten); + string actual = destination.ToString(); + + // Then + Assert.True(result); + Assert.Equal(expected, actual); + } + + [Fact(DisplayName = "String.TryCopyTo should return false if the string not is successfully copied to the target span")] + public void StringTryCopyToShouldReturnFalseIfStringIsNotSuccessfullyCopiedToTargetSpan() + { + // Given + const string expected = "Hello, World!"; + int expectedCharsWritten = 0; + Span destination = stackalloc char[expectedCharsWritten]; + + // When + bool result = expected.TryCopyTo(destination, out expectedCharsWritten); + + // Then + Assert.False(result); + } + + [Theory(DisplayName = "String.Wrap should wrap the current string value between the before and after string values")] + [InlineData("<", "value", ">", "")] + [InlineData("BEFORE:", "value", ":AFTER", "BEFORE:value:AFTER")] + public void WrapShouldProduceExpectedResult(string before, string value, string after, string expected) + { + // When + string actual = value.Wrap(before, after); + + // Then + Assert.Equal(expected, actual); + } } diff --git a/OnixLabs.Core.UnitTests/Text/Base16CodecInvariantTests.cs b/OnixLabs.Core.UnitTests/Text/Base16CodecInvariantTests.cs deleted file mode 100644 index 1e99222..0000000 --- a/OnixLabs.Core.UnitTests/Text/Base16CodecInvariantTests.cs +++ /dev/null @@ -1,58 +0,0 @@ -// Copyright 2020 ONIXLabs -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -using System.Text; -using OnixLabs.Core.Text; -using Xunit; - -namespace OnixLabs.Core.UnitTests.Text; - -public sealed class Base16CodecInvariantTests -{ - [Theory(DisplayName = "Base16Codec.Encode should produce the expected result")] - [InlineData("", "")] - [InlineData("ABCDEFGHIJKLMNOPQRSTUVWXYZ","4142434445464748494a4b4c4d4e4f505152535455565758595a")] - [InlineData("abcdefghijklmnopqrstuvwxyz","6162636465666768696a6b6c6d6e6f707172737475767778797a")] - [InlineData("0123456789","30313233343536373839")] - public void Base16CodecEncodeShouldProduceExpectedResult(string value, string expected) - { - // Given - IBaseCodec codec = IBaseCodec.Base16; - byte[] bytes = value.ToByteArray(); - - // When - string actual = codec.Encode(bytes, Base16FormatProvider.Invariant); - - // Then - Assert.Equal(expected, actual); - } - - [Theory(DisplayName = "Base16Codec.Decode should produce the expected result")] - [InlineData("", "")] - [InlineData("4142434445464748494a4b4c4d4e4f505152535455565758595a","ABCDEFGHIJKLMNOPQRSTUVWXYZ")] - [InlineData("6162636465666768696a6b6c6d6e6f707172737475767778797a","abcdefghijklmnopqrstuvwxyz")] - [InlineData("30313233343536373839","0123456789")] - public void Base16CodecDecodeShouldProduceExpectedResult(string value, string expected) - { - // Given - IBaseCodec codec = IBaseCodec.Base16; - - // When - byte[] bytes = codec.Decode(value, Base16FormatProvider.Invariant); - string actual = Encoding.UTF8.GetString(bytes); - - // Then - Assert.Equal(expected, actual); - } -} diff --git a/OnixLabs.Core.UnitTests/Text/Base16CodecLowercaseTests.cs b/OnixLabs.Core.UnitTests/Text/Base16CodecLowercaseTests.cs deleted file mode 100644 index a7a26c5..0000000 --- a/OnixLabs.Core.UnitTests/Text/Base16CodecLowercaseTests.cs +++ /dev/null @@ -1,58 +0,0 @@ -// Copyright 2020 ONIXLabs -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -using System.Text; -using OnixLabs.Core.Text; -using Xunit; - -namespace OnixLabs.Core.UnitTests.Text; - -public sealed class Base16CodecLowercaseTests -{ - [Theory(DisplayName = "Base16Codec.Encode should produce the expected result")] - [InlineData("", "")] - [InlineData("ABCDEFGHIJKLMNOPQRSTUVWXYZ","4142434445464748494a4b4c4d4e4f505152535455565758595a")] - [InlineData("abcdefghijklmnopqrstuvwxyz","6162636465666768696a6b6c6d6e6f707172737475767778797a")] - [InlineData("0123456789","30313233343536373839")] - public void Base16CodecEncodeShouldProduceExpectedResult(string value, string expected) - { - // Given - IBaseCodec codec = IBaseCodec.Base16; - byte[] bytes = value.ToByteArray(); - - // When - string actual = codec.Encode(bytes, Base16FormatProvider.Lowercase); - - // Then - Assert.Equal(expected, actual); - } - - [Theory(DisplayName = "Base16Codec.Decode should produce the expected result")] - [InlineData("", "")] - [InlineData("4142434445464748494a4b4c4d4e4f505152535455565758595a","ABCDEFGHIJKLMNOPQRSTUVWXYZ")] - [InlineData("6162636465666768696a6b6c6d6e6f707172737475767778797a","abcdefghijklmnopqrstuvwxyz")] - [InlineData("30313233343536373839","0123456789")] - public void Base16CodecDecodeShouldProduceExpectedResult(string value, string expected) - { - // Given - IBaseCodec codec = IBaseCodec.Base16; - - // When - byte[] bytes = codec.Decode(value, Base16FormatProvider.Lowercase); - string actual = Encoding.UTF8.GetString(bytes); - - // Then - Assert.Equal(expected, actual); - } -} diff --git a/OnixLabs.Core.UnitTests/Text/Base16CodecTests.cs b/OnixLabs.Core.UnitTests/Text/Base16CodecTests.cs new file mode 100644 index 0000000..39d6261 --- /dev/null +++ b/OnixLabs.Core.UnitTests/Text/Base16CodecTests.cs @@ -0,0 +1,218 @@ +// Copyright 2020 ONIXLabs +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +using System; +using System.Text; +using OnixLabs.Core.Text; +using OnixLabs.Core.UnitTests.Data; +using Xunit; + +namespace OnixLabs.Core.UnitTests.Text; + +public sealed class Base16CodecTests +{ + [Theory(DisplayName = "Base16Codec.Encode should produce the expected result (Invariant)")] + [InlineData("", "")] + [InlineData("ABCDEFGHIJKLMNOPQRSTUVWXYZ", "4142434445464748494a4b4c4d4e4f505152535455565758595a")] + [InlineData("abcdefghijklmnopqrstuvwxyz", "6162636465666768696a6b6c6d6e6f707172737475767778797a")] + [InlineData("0123456789", "30313233343536373839")] + public void Base16CodecEncodeShouldProduceExpectedResultInvariant(string value, string expected) + { + // Given + IBaseCodec codec = IBaseCodec.Base16; + byte[] bytes = value.ToByteArray(); + + // When + string actual = codec.Encode(bytes); + + // Then + Assert.Equal(expected, actual); + } + + [Theory(DisplayName = "Base16Codec.Decode should produce the expected result (Invariant)")] + [InlineData("", "")] + [InlineData("4142434445464748494a4b4c4d4e4f505152535455565758595a", "ABCDEFGHIJKLMNOPQRSTUVWXYZ")] + [InlineData("4142434445464748494A4B4C4D4E4F505152535455565758595A", "ABCDEFGHIJKLMNOPQRSTUVWXYZ")] + [InlineData("6162636465666768696a6b6c6d6e6f707172737475767778797a", "abcdefghijklmnopqrstuvwxyz")] + [InlineData("6162636465666768696A6B6C6D6E6F707172737475767778797A", "abcdefghijklmnopqrstuvwxyz")] + [InlineData("30313233343536373839", "0123456789")] + public void Base16CodecDecodeShouldProduceExpectedResultInvariant(string value, string expected) + { + // Given + IBaseCodec codec = IBaseCodec.Base16; + + // When + byte[] bytes = codec.Decode(value); + string actual = Encoding.UTF8.GetString(bytes); + + // Then + Assert.Equal(expected, actual); + } + + [Theory(DisplayName = "Base16Codec.Encode should produce the expected result (Lowercase)")] + [InlineData("", "")] + [InlineData("ABCDEFGHIJKLMNOPQRSTUVWXYZ", "4142434445464748494a4b4c4d4e4f505152535455565758595a")] + [InlineData("abcdefghijklmnopqrstuvwxyz", "6162636465666768696a6b6c6d6e6f707172737475767778797a")] + [InlineData("0123456789", "30313233343536373839")] + public void Base16CodecEncodeShouldProduceExpectedResultLowercase(string value, string expected) + { + // Given + IBaseCodec codec = IBaseCodec.Base16; + byte[] bytes = value.ToByteArray(); + + // When + string actual = codec.Encode(bytes, Base16FormatProvider.Lowercase); + + // Then + Assert.Equal(expected, actual); + } + + [Theory(DisplayName = "Base16Codec.Decode should produce the expected result (Lowercase)")] + [InlineData("", "")] + [InlineData("4142434445464748494a4b4c4d4e4f505152535455565758595a", "ABCDEFGHIJKLMNOPQRSTUVWXYZ")] + [InlineData("6162636465666768696a6b6c6d6e6f707172737475767778797a", "abcdefghijklmnopqrstuvwxyz")] + [InlineData("30313233343536373839", "0123456789")] + public void Base16CodecDecodeShouldProduceExpectedResultLowercase(string value, string expected) + { + // Given + IBaseCodec codec = IBaseCodec.Base16; + + // When + byte[] bytes = codec.Decode(value, Base16FormatProvider.Lowercase); + string actual = Encoding.UTF8.GetString(bytes); + + // Then + Assert.Equal(expected, actual); + } + + [Theory(DisplayName = "Base16Codec.Encode should produce the expected result (Uppercase)")] + [InlineData("", "")] + [InlineData("ABCDEFGHIJKLMNOPQRSTUVWXYZ", "4142434445464748494A4B4C4D4E4F505152535455565758595A")] + [InlineData("abcdefghijklmnopqrstuvwxyz", "6162636465666768696A6B6C6D6E6F707172737475767778797A")] + [InlineData("0123456789", "30313233343536373839")] + public void Base16CodecEncodeShouldProduceExpectedResultUppercase(string value, string expected) + { + // Given + IBaseCodec codec = IBaseCodec.Base16; + byte[] bytes = value.ToByteArray(); + + // When + string actual = codec.Encode(bytes, Base16FormatProvider.Uppercase); + + // Then + Assert.Equal(expected, actual); + } + + [Theory(DisplayName = "Base16Codec.Decode should produce the expected result (Uppercase)")] + [InlineData("", "")] + [InlineData("4142434445464748494A4B4C4D4E4F505152535455565758595A", "ABCDEFGHIJKLMNOPQRSTUVWXYZ")] + [InlineData("6162636465666768696A6B6C6D6E6F707172737475767778797A", "abcdefghijklmnopqrstuvwxyz")] + [InlineData("30313233343536373839", "0123456789")] + public void Base16CodecDecodeShouldProduceExpectedResultUppercase(string value, string expected) + { + // Given + IBaseCodec codec = IBaseCodec.Base16; + + // When + byte[] bytes = codec.Decode(value, Base16FormatProvider.Uppercase); + string actual = Encoding.UTF8.GetString(bytes); + + // Then + Assert.Equal(expected, actual); + } + + [Fact(DisplayName = "Base16Codec.Encode should throw FormatException when the format provider is invalid")] + public void Base16CodecEncodeShouldThrowFormatExceptionWhenFormatProviderIsInvalid() + { + // Given + IBaseCodec codec = IBaseCodec.Base16; + byte[] bytes = "Hello, World!".ToByteArray(); + + // When + Exception exception = Assert.Throws(() => codec.Encode(bytes, InvalidFormatProvider.Instance)); + + // Then + Assert.Equal("Encoding operation failed due to an invalid value or format provider.", exception.Message); + } + + [Fact(DisplayName = "Base16Codec.Decode should throw FormatException when the format provider is invalid")] + public void Base16CodecDecodeShouldThrowFormatExceptionWhenTheFormatProviderIsInvalid() + { + // Given + IBaseCodec codec = IBaseCodec.Base16; + const string value = "4142434445464748494a4b4c4d4e4f505152535455565758595a"; + + // When + Exception exception = Assert.Throws(() => codec.Decode(value, InvalidFormatProvider.Instance)); + + // Then + Assert.Equal("Decoding operation failed due to an invalid value or format provider.", exception.Message); + } + + [Fact(DisplayName = "Base16Codec.Decode should throw FormatException when the value is invalid")] + public void Base16CodecDecodeShouldThrowFormatExceptionWhenTheValueIsInvalid() + { + // Given + IBaseCodec codec = IBaseCodec.Base16; + const string value = "*INVALID_VALUE*"; + + // When + Exception exception = Assert.Throws(() => codec.Decode(value)); + + // Then + Assert.Equal("Decoding operation failed due to an invalid value or format provider.", exception.Message); + } + + [Fact(DisplayName = "Base16Codec.TryEncode should return false when the value is invalid")] + public void Base16CodecTryEncodeShouldReturnFalseWhenTheValueIsInvalid() + { + // Given + IBaseCodec codec = IBaseCodec.Base16; + byte[] bytes = new byte[1073741824]; + + // When + bool result = codec.TryEncode(bytes, Base16FormatProvider.Invariant, out string _); + + // Then + Assert.False(result); + } + + [Fact(DisplayName = "Base16Codec.TryDecode should return false when the value contains invalid characters (Lowercase)")] + public void Base16CodecTryDecodeShouldReturnFalseWhenValueContainsInvalidCharactersLowercase() + { + // Given + IBaseCodec codec = IBaseCodec.Base16; + const string value = "4142434445464748494A4B4C4D4E4F505152535455565758595A"; + + // When + bool result = codec.TryDecode(value, Base16FormatProvider.Lowercase, out byte[] _); + + // Then + Assert.False(result); + } + + [Fact(DisplayName = "Base16Codec.TryDecode should return false when the value contains invalid characters (Uppercase)")] + public void Base16CodecTryDecodeShouldReturnFalseWhenValueContainsInvalidCharactersUppercase() + { + // Given + IBaseCodec codec = IBaseCodec.Base16; + const string value = "4142434445464748494a4b4c4d4e4f505152535455565758595a"; + + // When + bool result = codec.TryDecode(value, Base16FormatProvider.Uppercase, out byte[] _); + + // Then + Assert.False(result); + } +} diff --git a/OnixLabs.Core.UnitTests/Text/Base16CodecUppercaseTests.cs b/OnixLabs.Core.UnitTests/Text/Base16CodecUppercaseTests.cs deleted file mode 100644 index 4176c93..0000000 --- a/OnixLabs.Core.UnitTests/Text/Base16CodecUppercaseTests.cs +++ /dev/null @@ -1,58 +0,0 @@ -// Copyright 2020 ONIXLabs -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -using System.Text; -using OnixLabs.Core.Text; -using Xunit; - -namespace OnixLabs.Core.UnitTests.Text; - -public sealed class Base16CodecUppercaseTests -{ - [Theory(DisplayName = "Base16Codec.Encode should produce the expected result")] - [InlineData("", "")] - [InlineData("ABCDEFGHIJKLMNOPQRSTUVWXYZ","4142434445464748494A4B4C4D4E4F505152535455565758595A")] - [InlineData("abcdefghijklmnopqrstuvwxyz","6162636465666768696A6B6C6D6E6F707172737475767778797A")] - [InlineData("0123456789","30313233343536373839")] - public void Base16CodecEncodeShouldProduceExpectedResult(string value, string expected) - { - // Given - IBaseCodec codec = IBaseCodec.Base16; - byte[] bytes = value.ToByteArray(); - - // When - string actual = codec.Encode(bytes, Base16FormatProvider.Uppercase); - - // Then - Assert.Equal(expected, actual); - } - - [Theory(DisplayName = "Base16Codec.Decode should produce the expected result")] - [InlineData("", "")] - [InlineData("4142434445464748494A4B4C4D4E4F505152535455565758595A","ABCDEFGHIJKLMNOPQRSTUVWXYZ")] - [InlineData("6162636465666768696A6B6C6D6E6F707172737475767778797A","abcdefghijklmnopqrstuvwxyz")] - [InlineData("30313233343536373839","0123456789")] - public void Base16CodecDecodeShouldProduceExpectedResult(string value, string expected) - { - // Given - IBaseCodec codec = IBaseCodec.Base16; - - // When - byte[] bytes = codec.Decode(value, Base16FormatProvider.Uppercase); - string actual = Encoding.UTF8.GetString(bytes); - - // Then - Assert.Equal(expected, actual); - } -} diff --git a/OnixLabs.Core.UnitTests/Text/Base16FormatProviderTests.cs b/OnixLabs.Core.UnitTests/Text/Base16FormatProviderTests.cs new file mode 100644 index 0000000..3ed6938 --- /dev/null +++ b/OnixLabs.Core.UnitTests/Text/Base16FormatProviderTests.cs @@ -0,0 +1,106 @@ +// Copyright 2020 ONIXLabs +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +using System; +using OnixLabs.Core.Text; +using Xunit; + +namespace OnixLabs.Core.UnitTests.Text; + +public sealed class Base16FormatProviderTests +{ + [Fact(DisplayName = "Base16FormatProvider.Invariant should produce the expected result")] + public void Base16FormatProviderInvariantShouldProduceExpectedAlphabet() + { + // Given + const int expectedValue = 0; + const string expectedName = "Invariant"; + const string expectedAlphabet = "0123456789ABCDEFabcdef"; + + // When + int actualValue = Base16FormatProvider.Invariant.Value; + string actualName = Base16FormatProvider.Invariant.Name; + string actualAlphabet = Base16FormatProvider.Invariant.Alphabet; + + // Then + Assert.Equal(expectedValue, actualValue); + Assert.Equal(expectedName, actualName); + Assert.Equal(expectedAlphabet, actualAlphabet); + } + + [Fact(DisplayName = "Base16FormatProvider.Uppercase should produce the expected result")] + public void Base16FormatProviderUppercaseShouldProduceExpectedAlphabet() + { + // Given + const int expectedValue = 1; + const string expectedName = "Uppercase"; + const string expectedAlphabet = "0123456789ABCDEF"; + + // When + int actualValue = Base16FormatProvider.Uppercase.Value; + string actualName = Base16FormatProvider.Uppercase.Name; + string actualAlphabet = Base16FormatProvider.Uppercase.Alphabet; + + // Then + Assert.Equal(expectedValue, actualValue); + Assert.Equal(expectedName, actualName); + Assert.Equal(expectedAlphabet, actualAlphabet); + } + + [Fact(DisplayName = "Base16FormatProvider.Lowercase should produce the expected result")] + public void Base16FormatProviderLowercaseShouldProduceExpectedAlphabet() + { + // Given + const int expectedValue = 2; + const string expectedName = "Lowercase"; + const string expectedAlphabet = "0123456789abcdef"; + + // When + int actualValue = Base16FormatProvider.Lowercase.Value; + string actualName = Base16FormatProvider.Lowercase.Name; + string actualAlphabet = Base16FormatProvider.Lowercase.Alphabet; + + // Then + Assert.Equal(expectedValue, actualValue); + Assert.Equal(expectedName, actualName); + Assert.Equal(expectedAlphabet, actualAlphabet); + } + + [Fact(DisplayName = "Base16FormatProvider.GetFormat should return Base16FormatProvider when the given type is Base16FormatProvider")] + public void Base16FormatProviderShouldReturnBase16FormatProviderWhenGivenTypeIsBase16FormatProvider() + { + // Given + Type type = typeof(Base16FormatProvider); + + // When + object? result = Base16FormatProvider.Invariant.GetFormat(type); + + // Then + Assert.NotNull(result); + Assert.IsType(result); + } + + [Fact(DisplayName = "Base16FormatProvider.GetFormat should return null when the given type is not Base16FormatProvider")] + public void Base16FormatProviderShouldReturnNullWhenGivenTypeIsNotBase16FormatProvider() + { + // Given + Type type = typeof(object); + + // When + object? result = Base16FormatProvider.Invariant.GetFormat(type); + + // Then + Assert.Null(result); + } +} diff --git a/OnixLabs.Core.UnitTests/Text/Base16Tests.cs b/OnixLabs.Core.UnitTests/Text/Base16Tests.cs index e88a206..a9b295c 100644 --- a/OnixLabs.Core.UnitTests/Text/Base16Tests.cs +++ b/OnixLabs.Core.UnitTests/Text/Base16Tests.cs @@ -12,6 +12,8 @@ // See the License for the specific language governing permissions and // limitations under the License. +using System; +using System.Buffers; using System.Text; using OnixLabs.Core.Text; using Xunit; @@ -20,6 +22,81 @@ namespace OnixLabs.Core.UnitTests.Text; public sealed class Base16Tests { + [Fact(DisplayName = "Base16 should be implicitly constructable from byte[]")] + public void Base16ShouldBeImplicitlyConstructableFromByteArray() + { + // Given + const string expected = "414243616263313233"; + byte[] value = "ABCabc123".ToByteArray(); + + // When + Base16 candidate = value; + string actual = candidate.ToString(); + + // Then + Assert.Equal(expected, actual); + } + + [Fact(DisplayName = "Base16 should be implicitly constructable from ReadOnlySpan")] + public void Base16ShouldBeImplicitlyConstructableFromReadOnlySpanOfByte() + { + // Given + const string expected = "414243616263313233"; + ReadOnlySpan value = "ABCabc123".ToByteArray().AsSpan(); + + // When + Base16 candidate = value; + string actual = candidate.ToString(); + + // Then + Assert.Equal(expected, actual); + } + + [Fact(DisplayName = "Base16 should be implicitly constructable from string")] + public void Base16ShouldBeImplicitlyConstructableFromString() + { + // Given + const string expected = "414243616263313233"; + const string value = "ABCabc123"; + + // When + Base16 candidate = value; + string actual = candidate.ToString(); + + // Then + Assert.Equal(expected, actual); + } + + [Fact(DisplayName = "Base16 should be implicitly constructable from char[]")] + public void Base16ShouldBeImplicitlyConstructableFromCharArray() + { + // Given + const string expected = "414243616263313233"; + char[] value = "ABCabc123".ToCharArray(); + + // When + Base16 candidate = value; + string actual = candidate.ToString(); + + // Then + Assert.Equal(expected, actual); + } + + [Fact(DisplayName = "Base16 should be implicitly constructable from ReadOnlySequence")] + public void Base16ShouldBeImplicitlyConstructableFromReadOnlySequenceOfChar() + { + // Given + const string expected = "414243616263313233"; + ReadOnlySequence value = new("ABCabc123".ToCharArray()); + + // When + Base16 candidate = value; + string actual = candidate.ToString(); + + // Then + Assert.Equal(expected, actual); + } + [Fact(DisplayName = "Base16 should not change when modifying the original byte array")] public void Base16ShouldNotChangeWhenModifyingOriginalByteArray() { @@ -99,9 +176,9 @@ public void Base16ValuesShouldNotBeIdentical() [Theory(DisplayName = "Base16.Parse should produce the expected result")] [InlineData("", "")] - [InlineData("4142434445464748494a4b4c4d4e4f505152535455565758595a","ABCDEFGHIJKLMNOPQRSTUVWXYZ")] - [InlineData("6162636465666768696a6b6c6d6e6f707172737475767778797a","abcdefghijklmnopqrstuvwxyz")] - [InlineData("30313233343536373839","0123456789")] + [InlineData("4142434445464748494a4b4c4d4e4f505152535455565758595a", "ABCDEFGHIJKLMNOPQRSTUVWXYZ")] + [InlineData("6162636465666768696a6b6c6d6e6f707172737475767778797a", "abcdefghijklmnopqrstuvwxyz")] + [InlineData("30313233343536373839", "0123456789")] public void Base16ParseShouldProduceExpectedResult(string value, string expected) { // Given @@ -116,9 +193,9 @@ public void Base16ParseShouldProduceExpectedResult(string value, string expected [Theory(DisplayName = "Base16.ToString should produce the expected result")] [InlineData("", "")] - [InlineData("ABCDEFGHIJKLMNOPQRSTUVWXYZ","4142434445464748494a4b4c4d4e4f505152535455565758595a")] - [InlineData("abcdefghijklmnopqrstuvwxyz","6162636465666768696a6b6c6d6e6f707172737475767778797a")] - [InlineData("0123456789","30313233343536373839")] + [InlineData("ABCDEFGHIJKLMNOPQRSTUVWXYZ", "4142434445464748494a4b4c4d4e4f505152535455565758595a")] + [InlineData("abcdefghijklmnopqrstuvwxyz", "6162636465666768696a6b6c6d6e6f707172737475767778797a")] + [InlineData("0123456789", "30313233343536373839")] public void Base16ToStringShouldProduceExpectedResult(string value, string expected) { // Given @@ -131,4 +208,105 @@ public void Base16ToStringShouldProduceExpectedResult(string value, string expec // Then Assert.Equal(expected, actual); } + + [Theory(DisplayName = "Base16.TryFormat should produce the expected result when the value was formatted correctly")] + [InlineData("", "")] + [InlineData("ABCDEFGHIJKLMNOPQRSTUVWXYZ", "4142434445464748494a4b4c4d4e4f505152535455565758595a")] + [InlineData("abcdefghijklmnopqrstuvwxyz", "6162636465666768696a6b6c6d6e6f707172737475767778797a")] + [InlineData("0123456789", "30313233343536373839")] + public void Base16TryFormatShouldProduceExpectedResultWhenValueWasFormattedCorrectly(string value, string expected) + { + // Given + ISpanFormattable candidate = new Base16(Encoding.UTF8.GetBytes(value)); + Span destination = stackalloc char[expected.Length]; + + // When + bool result = candidate.TryFormat(destination, out int charsWritten, [], null); + string actual = destination.ToString(); + + // Then + Assert.True(result); + Assert.Equal(expected, actual); + Assert.Equal(expected.Length, charsWritten); + } + + [Theory(DisplayName = "Base16.TryFormat should produce the expected result when the value was not formatted correctly")] + [InlineData("ABCDEFGHIJKLMNOPQRSTUVWXYZ", "4142434445464748494a4b4c4d4e4f505152535455565758595a")] + [InlineData("abcdefghijklmnopqrstuvwxyz", "6162636465666768696a6b6c6d6e6f707172737475767778797a")] + [InlineData("0123456789", "30313233343536373839")] + public void Base16TryFormatShouldProduceExpectedResultWhenValueWasNotFormattedCorrectly(string value, string expected) + { + // Given + ISpanFormattable candidate = new Base16(Encoding.UTF8.GetBytes(value)); + Span destination = stackalloc char[0]; + + // When + bool result = candidate.TryFormat(destination, out int charsWritten, [], null); + string actual = destination.ToString(); + + // Then + Assert.False(result); + Assert.NotEqual(expected, actual); + Assert.Equal(0, charsWritten); + } + + [Theory(DisplayName = "Base16.TryParse should produce the expected result when the string is parsed successfully")] + [InlineData("", "")] + [InlineData("ABCDEFGHIJKLMNOPQRSTUVWXYZ", "4142434445464748494a4b4c4d4e4f505152535455565758595a")] + [InlineData("abcdefghijklmnopqrstuvwxyz", "6162636465666768696a6b6c6d6e6f707172737475767778797a")] + [InlineData("0123456789", "30313233343536373839")] + public void Base16TryParseShouldProduceTheExpectedResultWhenStringIsParsedSuccessfully(string expected, string value) + { + // When + bool result = Base16.TryParse(value, null, out Base16 candidate); + string actual = Encoding.UTF8.GetString(candidate.ToByteArray()); + + // Then + Assert.True(result); + Assert.Equal(expected, actual); + } + + [Theory(DisplayName = "Base16.TryParse should produce the expected result when the string is not parsed successfully")] + [InlineData("", "*INVALID_VALUE*")] + public void Base16TryParseShouldProduceTheExpectedResultWhenStringIsNotParsedSuccessfully(string expected, string value) + { + // When + bool result = Base16.TryParse(value, null, out Base16 candidate); + string actual = Encoding.UTF8.GetString(candidate.ToByteArray()); + + // Then + Assert.False(result); + Assert.Equal(expected, actual); + Assert.Equal(default, candidate); + } + + [Theory(DisplayName = "Base16.TryParse should produce the expected result when the ReadOnlySpan is parsed successfully")] + [InlineData("", "")] + [InlineData("ABCDEFGHIJKLMNOPQRSTUVWXYZ", "4142434445464748494a4b4c4d4e4f505152535455565758595a")] + [InlineData("abcdefghijklmnopqrstuvwxyz", "6162636465666768696a6b6c6d6e6f707172737475767778797a")] + [InlineData("0123456789", "30313233343536373839")] + public void Base16TryParseShouldProduceTheExpectedResultWhenReadOnlySpanOfCharIsParsedSuccessfully(string expected, string value) + { + // When + bool result = Base16.TryParse(value.AsSpan(), null, out Base16 candidate); + string actual = Encoding.UTF8.GetString(candidate.ToByteArray()); + + // Then + Assert.True(result); + Assert.Equal(expected, actual); + } + + [Theory(DisplayName = "Base16.TryParse should produce the expected result when the ReadOnlySpan is not parsed successfully")] + [InlineData("", "*INVALID_VALUE*")] + public void Base16TryParseShouldProduceTheExpectedResultWhenReadOnlySpanOfCharIsNotParsedSuccessfully(string expected, string value) + { + // When + bool result = Base16.TryParse(value.AsSpan(), null, out Base16 candidate); + string actual = Encoding.UTF8.GetString(candidate.ToByteArray()); + + // Then + Assert.False(result); + Assert.Equal(expected, actual); + Assert.Equal(default, candidate); + } } diff --git a/OnixLabs.Core.UnitTests/Text/Base32CodecBase32HexTests.cs b/OnixLabs.Core.UnitTests/Text/Base32CodecBase32HexTests.cs deleted file mode 100644 index a5abccb..0000000 --- a/OnixLabs.Core.UnitTests/Text/Base32CodecBase32HexTests.cs +++ /dev/null @@ -1,58 +0,0 @@ -// Copyright 2020 ONIXLabs -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -using System.Text; -using OnixLabs.Core.Text; -using Xunit; - -namespace OnixLabs.Core.UnitTests.Text; - -public sealed class Base32CodecBase32HexTests -{ - [Theory(DisplayName = "Base32Codec.Encode should produce the expected result")] - [InlineData("", "")] - [InlineData("ABCDEFGHIJKLMNOPQRSTUVWXYZ","85146H258P3KGIAA9D64QJIFA18L4KQKALB5EM2PB8")] - [InlineData("abcdefghijklmnopqrstuvwxyz","C5H66P35CPJMGQBADDM6QRJFE1ON4SRKELR7EU3PF8")] - [InlineData("0123456789","60OJ4CPK6KR3EE1P")] - public void Base32CodecEncodeShouldProduceExpectedResult(string value, string expected) - { - // Given - IBaseCodec codec = IBaseCodec.Base32; - byte[] bytes = value.ToByteArray(); - - // When - string actual = codec.Encode(bytes, Base32FormatProvider.Base32Hex); - - // Then - Assert.Equal(expected, actual); - } - - [Theory(DisplayName = "Base32Codec.Decode should produce the expected result")] - [InlineData("", "")] - [InlineData("85146H258P3KGIAA9D64QJIFA18L4KQKALB5EM2PB8","ABCDEFGHIJKLMNOPQRSTUVWXYZ")] - [InlineData("C5H66P35CPJMGQBADDM6QRJFE1ON4SRKELR7EU3PF8","abcdefghijklmnopqrstuvwxyz")] - [InlineData("60OJ4CPK6KR3EE1P","0123456789")] - public void Base32CodecDecodeShouldProduceExpectedResult(string value, string expected) - { - // Given - IBaseCodec codec = IBaseCodec.Base32; - - // When - byte[] bytes = codec.Decode(value, Base32FormatProvider.Base32Hex); - string actual = Encoding.UTF8.GetString(bytes); - - // Then - Assert.Equal(expected, actual); - } -} diff --git a/OnixLabs.Core.UnitTests/Text/Base32CodecCrockfordTests.cs b/OnixLabs.Core.UnitTests/Text/Base32CodecCrockfordTests.cs deleted file mode 100644 index b75133f..0000000 --- a/OnixLabs.Core.UnitTests/Text/Base32CodecCrockfordTests.cs +++ /dev/null @@ -1,58 +0,0 @@ -// Copyright 2020 ONIXLabs -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -using System.Text; -using OnixLabs.Core.Text; -using Xunit; - -namespace OnixLabs.Core.UnitTests.Text; - -public sealed class Base32CodecCrockfordTests -{ - [Theory(DisplayName = "Base32Codec.Encode should produce the expected result")] - [InlineData("", "")] - [InlineData("ABCDEFGHIJKLMNOPQRSTUVWXYZ","85146H258S3MGJAA9D64TKJFA18N4MTMANB5EP2SB8")] - [InlineData("abcdefghijklmnopqrstuvwxyz","C5H66S35CSKPGTBADDP6TVKFE1RQ4WVMENV7EY3SF8")] - [InlineData("0123456789","60RK4CSM6MV3EE1S")] - public void Base32CodecEncodeShouldProduceExpectedResult(string value, string expected) - { - // Given - IBaseCodec codec = IBaseCodec.Base32; - byte[] bytes = value.ToByteArray(); - - // When - string actual = codec.Encode(bytes, Base32FormatProvider.Crockford); - - // Then - Assert.Equal(expected, actual); - } - - [Theory(DisplayName = "Base32Codec.Decode should produce the expected result")] - [InlineData("", "")] - [InlineData("85146H258S3MGJAA9D64TKJFA18N4MTMANB5EP2SB8","ABCDEFGHIJKLMNOPQRSTUVWXYZ")] - [InlineData("C5H66S35CSKPGTBADDP6TVKFE1RQ4WVMENV7EY3SF8","abcdefghijklmnopqrstuvwxyz")] - [InlineData("60RK4CSM6MV3EE1S","0123456789")] - public void Base32CodecDecodeShouldProduceExpectedResult(string value, string expected) - { - // Given - IBaseCodec codec = IBaseCodec.Base32; - - // When - byte[] bytes = codec.Decode(value, Base32FormatProvider.Crockford); - string actual = Encoding.UTF8.GetString(bytes); - - // Then - Assert.Equal(expected, actual); - } -} diff --git a/OnixLabs.Core.UnitTests/Text/Base32CodecGeoHashTests.cs b/OnixLabs.Core.UnitTests/Text/Base32CodecGeoHashTests.cs deleted file mode 100644 index 1aa3b0e..0000000 --- a/OnixLabs.Core.UnitTests/Text/Base32CodecGeoHashTests.cs +++ /dev/null @@ -1,58 +0,0 @@ -// Copyright 2020 ONIXLabs -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -using System.Text; -using OnixLabs.Core.Text; -using Xunit; - -namespace OnixLabs.Core.UnitTests.Text; - -public sealed class Base32CodecGeoHashTests -{ - [Theory(DisplayName = "Base32Codec.Encode should produce the expected result")] - [InlineData("", "")] - [InlineData("ABCDEFGHIJKLMNOPQRSTUVWXYZ","85146j258t3nhkbb9e64umkgb18p4nunbpc5fq2tc8")] - [InlineData("abcdefghijklmnopqrstuvwxyz","d5j66t35dtmqhucbeeq6uvmgf1sr4wvnfpv7fy3tg8")] - [InlineData("0123456789","60sm4dtn6nv3ff1t")] - public void Base32CodecEncodeShouldProduceExpectedResult(string value, string expected) - { - // Given - IBaseCodec codec = IBaseCodec.Base32; - byte[] bytes = value.ToByteArray(); - - // When - string actual = codec.Encode(bytes, Base32FormatProvider.GeoHash); - - // Then - Assert.Equal(expected, actual); - } - - [Theory(DisplayName = "Base32Codec.Decode should produce the expected result")] - [InlineData("", "")] - [InlineData("85146j258t3nhkbb9e64umkgb18p4nunbpc5fq2tc8","ABCDEFGHIJKLMNOPQRSTUVWXYZ")] - [InlineData("d5j66t35dtmqhucbeeq6uvmgf1sr4wvnfpv7fy3tg8","abcdefghijklmnopqrstuvwxyz")] - [InlineData("60sm4dtn6nv3ff1t","0123456789")] - public void Base32CodecDecodeShouldProduceExpectedResult(string value, string expected) - { - // Given - IBaseCodec codec = IBaseCodec.Base32; - - // When - byte[] bytes = codec.Decode(value, Base32FormatProvider.GeoHash); - string actual = Encoding.UTF8.GetString(bytes); - - // Then - Assert.Equal(expected, actual); - } -} diff --git a/OnixLabs.Core.UnitTests/Text/Base32CodecPaddedBase32HexTests.cs b/OnixLabs.Core.UnitTests/Text/Base32CodecPaddedBase32HexTests.cs deleted file mode 100644 index d1b68db..0000000 --- a/OnixLabs.Core.UnitTests/Text/Base32CodecPaddedBase32HexTests.cs +++ /dev/null @@ -1,58 +0,0 @@ -// Copyright 2020 ONIXLabs -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -using System.Text; -using OnixLabs.Core.Text; -using Xunit; - -namespace OnixLabs.Core.UnitTests.Text; - -public sealed class Base32CodecPaddedBase32HexTests -{ - [Theory(DisplayName = "Base32Codec.Encode should produce the expected result")] - [InlineData("", "")] - [InlineData("ABCDEFGHIJKLMNOPQRSTUVWXYZ","85146H258P3KGIAA9D64QJIFA18L4KQKALB5EM2PB8======")] - [InlineData("abcdefghijklmnopqrstuvwxyz","C5H66P35CPJMGQBADDM6QRJFE1ON4SRKELR7EU3PF8======")] - [InlineData("0123456789","60OJ4CPK6KR3EE1P")] - public void Base32CodecEncodeShouldProduceExpectedResult(string value, string expected) - { - // Given - IBaseCodec codec = IBaseCodec.Base32; - byte[] bytes = value.ToByteArray(); - - // When - string actual = codec.Encode(bytes, Base32FormatProvider.PaddedBase32Hex); - - // Then - Assert.Equal(expected, actual); - } - - [Theory(DisplayName = "Base32Codec.Decode should produce the expected result")] - [InlineData("", "")] - [InlineData("85146H258P3KGIAA9D64QJIFA18L4KQKALB5EM2PB8======","ABCDEFGHIJKLMNOPQRSTUVWXYZ")] - [InlineData("C5H66P35CPJMGQBADDM6QRJFE1ON4SRKELR7EU3PF8======","abcdefghijklmnopqrstuvwxyz")] - [InlineData("60OJ4CPK6KR3EE1P","0123456789")] - public void Base32CodecDecodeShouldProduceExpectedResult(string value, string expected) - { - // Given - IBaseCodec codec = IBaseCodec.Base32; - - // When - byte[] bytes = codec.Decode(value, Base32FormatProvider.PaddedBase32Hex); - string actual = Encoding.UTF8.GetString(bytes); - - // Then - Assert.Equal(expected, actual); - } -} diff --git a/OnixLabs.Core.UnitTests/Text/Base32CodecPaddedCrockfordTests.cs b/OnixLabs.Core.UnitTests/Text/Base32CodecPaddedCrockfordTests.cs deleted file mode 100644 index ba48e0f..0000000 --- a/OnixLabs.Core.UnitTests/Text/Base32CodecPaddedCrockfordTests.cs +++ /dev/null @@ -1,58 +0,0 @@ -// Copyright 2020 ONIXLabs -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -using System.Text; -using OnixLabs.Core.Text; -using Xunit; - -namespace OnixLabs.Core.UnitTests.Text; - -public sealed class Base32CodecPaddedCrockfordTests -{ - [Theory(DisplayName = "Base32Codec.Encode should produce the expected result")] - [InlineData("", "")] - [InlineData("ABCDEFGHIJKLMNOPQRSTUVWXYZ","85146H258S3MGJAA9D64TKJFA18N4MTMANB5EP2SB8======")] - [InlineData("abcdefghijklmnopqrstuvwxyz","C5H66S35CSKPGTBADDP6TVKFE1RQ4WVMENV7EY3SF8======")] - [InlineData("0123456789","60RK4CSM6MV3EE1S")] - public void Base32CodecEncodeShouldProduceExpectedResult(string value, string expected) - { - // Given - IBaseCodec codec = IBaseCodec.Base32; - byte[] bytes = value.ToByteArray(); - - // When - string actual = codec.Encode(bytes, Base32FormatProvider.PaddedCrockford); - - // Then - Assert.Equal(expected, actual); - } - - [Theory(DisplayName = "Base32Codec.Decode should produce the expected result")] - [InlineData("", "")] - [InlineData("85146H258S3MGJAA9D64TKJFA18N4MTMANB5EP2SB8======","ABCDEFGHIJKLMNOPQRSTUVWXYZ")] - [InlineData("C5H66S35CSKPGTBADDP6TVKFE1RQ4WVMENV7EY3SF8======","abcdefghijklmnopqrstuvwxyz")] - [InlineData("60RK4CSM6MV3EE1S","0123456789")] - public void Base32CodecDecodeShouldProduceExpectedResult(string value, string expected) - { - // Given - IBaseCodec codec = IBaseCodec.Base32; - - // When - byte[] bytes = codec.Decode(value, Base32FormatProvider.PaddedCrockford); - string actual = Encoding.UTF8.GetString(bytes); - - // Then - Assert.Equal(expected, actual); - } -} diff --git a/OnixLabs.Core.UnitTests/Text/Base32CodecPaddedGeoHashTests.cs b/OnixLabs.Core.UnitTests/Text/Base32CodecPaddedGeoHashTests.cs deleted file mode 100644 index 07c820b..0000000 --- a/OnixLabs.Core.UnitTests/Text/Base32CodecPaddedGeoHashTests.cs +++ /dev/null @@ -1,58 +0,0 @@ -// Copyright 2020 ONIXLabs -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -using System.Text; -using OnixLabs.Core.Text; -using Xunit; - -namespace OnixLabs.Core.UnitTests.Text; - -public sealed class Base32CodecPaddedGeoHashTests -{ - [Theory(DisplayName = "Base32Codec.Encode should produce the expected result")] - [InlineData("", "")] - [InlineData("ABCDEFGHIJKLMNOPQRSTUVWXYZ","85146j258t3nhkbb9e64umkgb18p4nunbpc5fq2tc8======")] - [InlineData("abcdefghijklmnopqrstuvwxyz","d5j66t35dtmqhucbeeq6uvmgf1sr4wvnfpv7fy3tg8======")] - [InlineData("0123456789","60sm4dtn6nv3ff1t")] - public void Base32CodecEncodeShouldProduceExpectedResult(string value, string expected) - { - // Given - IBaseCodec codec = IBaseCodec.Base32; - byte[] bytes = value.ToByteArray(); - - // When - string actual = codec.Encode(bytes, Base32FormatProvider.PaddedGeoHash); - - // Then - Assert.Equal(expected, actual); - } - - [Theory(DisplayName = "Base32Codec.Decode should produce the expected result")] - [InlineData("", "")] - [InlineData("85146j258t3nhkbb9e64umkgb18p4nunbpc5fq2tc8======","ABCDEFGHIJKLMNOPQRSTUVWXYZ")] - [InlineData("d5j66t35dtmqhucbeeq6uvmgf1sr4wvnfpv7fy3tg8======","abcdefghijklmnopqrstuvwxyz")] - [InlineData("60sm4dtn6nv3ff1t","0123456789")] - public void Base32CodecDecodeShouldProduceExpectedResult(string value, string expected) - { - // Given - IBaseCodec codec = IBaseCodec.Base32; - - // When - byte[] bytes = codec.Decode(value, Base32FormatProvider.PaddedGeoHash); - string actual = Encoding.UTF8.GetString(bytes); - - // Then - Assert.Equal(expected, actual); - } -} diff --git a/OnixLabs.Core.UnitTests/Text/Base32CodecPaddedRfc4648Tests.cs b/OnixLabs.Core.UnitTests/Text/Base32CodecPaddedRfc4648Tests.cs deleted file mode 100644 index da0ba10..0000000 --- a/OnixLabs.Core.UnitTests/Text/Base32CodecPaddedRfc4648Tests.cs +++ /dev/null @@ -1,58 +0,0 @@ -// Copyright 2020 ONIXLabs -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -using System.Text; -using OnixLabs.Core.Text; -using Xunit; - -namespace OnixLabs.Core.UnitTests.Text; - -public sealed class Base32CodecPaddedRfc4648Tests -{ - [Theory(DisplayName = "Base32Codec.Encode should produce the expected result")] - [InlineData("", "")] - [InlineData("ABCDEFGHIJKLMNOPQRSTUVWXYZ","IFBEGRCFIZDUQSKKJNGE2TSPKBIVEU2UKVLFOWCZLI======")] - [InlineData("abcdefghijklmnopqrstuvwxyz","MFRGGZDFMZTWQ2LKNNWG23TPOBYXE43UOV3HO6DZPI======")] - [InlineData("0123456789","GAYTEMZUGU3DOOBZ")] - public void Base32CodecEncodeShouldProduceExpectedResult(string value, string expected) - { - // Given - IBaseCodec codec = IBaseCodec.Base32; - byte[] bytes = value.ToByteArray(); - - // When - string actual = codec.Encode(bytes, Base32FormatProvider.PaddedRfc4648); - - // Then - Assert.Equal(expected, actual); - } - - [Theory(DisplayName = "Base32Codec.Decode should produce the expected result")] - [InlineData("", "")] - [InlineData("IFBEGRCFIZDUQSKKJNGE2TSPKBIVEU2UKVLFOWCZLI======","ABCDEFGHIJKLMNOPQRSTUVWXYZ")] - [InlineData("MFRGGZDFMZTWQ2LKNNWG23TPOBYXE43UOV3HO6DZPI======","abcdefghijklmnopqrstuvwxyz")] - [InlineData("GAYTEMZUGU3DOOBZ","0123456789")] - public void Base32CodecDecodeShouldProduceExpectedResult(string value, string expected) - { - // Given - IBaseCodec codec = IBaseCodec.Base32; - - // When - byte[] bytes = codec.Decode(value, Base32FormatProvider.PaddedRfc4648); - string actual = Encoding.UTF8.GetString(bytes); - - // Then - Assert.Equal(expected, actual); - } -} diff --git a/OnixLabs.Core.UnitTests/Text/Base32CodecPaddedZBase32Tests.cs b/OnixLabs.Core.UnitTests/Text/Base32CodecPaddedZBase32Tests.cs deleted file mode 100644 index 80838c9..0000000 --- a/OnixLabs.Core.UnitTests/Text/Base32CodecPaddedZBase32Tests.cs +++ /dev/null @@ -1,58 +0,0 @@ -// Copyright 2020 ONIXLabs -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -using System.Text; -using OnixLabs.Core.Text; -using Xunit; - -namespace OnixLabs.Core.UnitTests.Text; - -public sealed class Base32CodecPaddedZBase32Tests -{ - [Theory(DisplayName = "Base32Codec.Encode should produce the expected result")] - [InlineData("", "")] - [InlineData("ABCDEFGHIJKLMNOPQRSTUVWXYZ","efbrgtnfe3dwo1kkjpgr4u1xkbeirw4wkimfqsn3me======")] - [InlineData("abcdefghijklmnopqrstuvwxyz","cftgg3dfc3uso4mkppsg45uxqbazrh5wqi58q6d3xe======")] - [InlineData("0123456789","gyaurc3wgw5dqqb3")] - public void Base32CodecEncodeShouldProduceExpectedResult(string value, string expected) - { - // Given - IBaseCodec codec = IBaseCodec.Base32; - byte[] bytes = value.ToByteArray(); - - // When - string actual = codec.Encode(bytes, Base32FormatProvider.PaddedZBase32); - - // Then - Assert.Equal(expected, actual); - } - - [Theory(DisplayName = "Base32Codec.Decode should produce the expected result")] - [InlineData("", "")] - [InlineData("efbrgtnfe3dwo1kkjpgr4u1xkbeirw4wkimfqsn3me======","ABCDEFGHIJKLMNOPQRSTUVWXYZ")] - [InlineData("cftgg3dfc3uso4mkppsg45uxqbazrh5wqi58q6d3xe======","abcdefghijklmnopqrstuvwxyz")] - [InlineData("gyaurc3wgw5dqqb3","0123456789")] - public void Base32CodecDecodeShouldProduceExpectedResult(string value, string expected) - { - // Given - IBaseCodec codec = IBaseCodec.Base32; - - // When - byte[] bytes = codec.Decode(value, Base32FormatProvider.PaddedZBase32); - string actual = Encoding.UTF8.GetString(bytes); - - // Then - Assert.Equal(expected, actual); - } -} diff --git a/OnixLabs.Core.UnitTests/Text/Base32CodecRfc4648Tests.cs b/OnixLabs.Core.UnitTests/Text/Base32CodecRfc4648Tests.cs deleted file mode 100644 index 6a15981..0000000 --- a/OnixLabs.Core.UnitTests/Text/Base32CodecRfc4648Tests.cs +++ /dev/null @@ -1,58 +0,0 @@ -// Copyright 2020 ONIXLabs -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -using System.Text; -using OnixLabs.Core.Text; -using Xunit; - -namespace OnixLabs.Core.UnitTests.Text; - -public sealed class Base32CodecRfc4648Tests -{ - [Theory(DisplayName = "Base32Codec.Encode should produce the expected result")] - [InlineData("", "")] - [InlineData("ABCDEFGHIJKLMNOPQRSTUVWXYZ","IFBEGRCFIZDUQSKKJNGE2TSPKBIVEU2UKVLFOWCZLI")] - [InlineData("abcdefghijklmnopqrstuvwxyz","MFRGGZDFMZTWQ2LKNNWG23TPOBYXE43UOV3HO6DZPI")] - [InlineData("0123456789","GAYTEMZUGU3DOOBZ")] - public void Base32CodecEncodeShouldProduceExpectedResult(string value, string expected) - { - // Given - IBaseCodec codec = IBaseCodec.Base32; - byte[] bytes = value.ToByteArray(); - - // When - string actual = codec.Encode(bytes); - - // Then - Assert.Equal(expected, actual); - } - - [Theory(DisplayName = "Base32Codec.Decode should produce the expected result")] - [InlineData("", "")] - [InlineData("IFBEGRCFIZDUQSKKJNGE2TSPKBIVEU2UKVLFOWCZLI","ABCDEFGHIJKLMNOPQRSTUVWXYZ")] - [InlineData("MFRGGZDFMZTWQ2LKNNWG23TPOBYXE43UOV3HO6DZPI","abcdefghijklmnopqrstuvwxyz")] - [InlineData("GAYTEMZUGU3DOOBZ","0123456789")] - public void Base32CodecDecodeShouldProduceExpectedResult(string value, string expected) - { - // Given - IBaseCodec codec = IBaseCodec.Base32; - - // When - byte[] bytes = codec.Decode(value); - string actual = Encoding.UTF8.GetString(bytes); - - // Then - Assert.Equal(expected, actual); - } -} diff --git a/OnixLabs.Core.UnitTests/Text/Base32CodecTests.cs b/OnixLabs.Core.UnitTests/Text/Base32CodecTests.cs new file mode 100644 index 0000000..9e6c6da --- /dev/null +++ b/OnixLabs.Core.UnitTests/Text/Base32CodecTests.cs @@ -0,0 +1,494 @@ +// Copyright 2020 ONIXLabs +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +using System; +using System.Text; +using OnixLabs.Core.Text; +using OnixLabs.Core.UnitTests.Data; +using Xunit; + +namespace OnixLabs.Core.UnitTests.Text; + +public sealed class Base32CodecTests +{ + [Theory(DisplayName = "Base32Codec.Encode should produce the expected result (Rfc4648)")] + [InlineData("", "")] + [InlineData("ABCDEFGHIJKLMNOPQRSTUVWXYZ", "IFBEGRCFIZDUQSKKJNGE2TSPKBIVEU2UKVLFOWCZLI")] + [InlineData("abcdefghijklmnopqrstuvwxyz", "MFRGGZDFMZTWQ2LKNNWG23TPOBYXE43UOV3HO6DZPI")] + [InlineData("0123456789", "GAYTEMZUGU3DOOBZ")] + public void Base32CodecEncodeShouldProduceExpectedResultRfc4648(string value, string expected) + { + // Given + IBaseCodec codec = IBaseCodec.Base32; + byte[] bytes = value.ToByteArray(); + + // When + string actual = codec.Encode(bytes); + + // Then + Assert.Equal(expected, actual); + } + + [Theory(DisplayName = "Base32Codec.Decode should produce the expected result (Rfc4648)")] + [InlineData("", "")] + [InlineData("IFBEGRCFIZDUQSKKJNGE2TSPKBIVEU2UKVLFOWCZLI", "ABCDEFGHIJKLMNOPQRSTUVWXYZ")] + [InlineData("MFRGGZDFMZTWQ2LKNNWG23TPOBYXE43UOV3HO6DZPI", "abcdefghijklmnopqrstuvwxyz")] + [InlineData("GAYTEMZUGU3DOOBZ", "0123456789")] + public void Base32CodecDecodeShouldProduceExpectedResultRfc4648(string value, string expected) + { + // Given + IBaseCodec codec = IBaseCodec.Base32; + + // When + byte[] bytes = codec.Decode(value); + string actual = Encoding.UTF8.GetString(bytes); + + // Then + Assert.Equal(expected, actual); + } + + [Theory(DisplayName = "Base32Codec.Encode should produce the expected result (Base32Hex)")] + [InlineData("", "")] + [InlineData("ABCDEFGHIJKLMNOPQRSTUVWXYZ", "85146H258P3KGIAA9D64QJIFA18L4KQKALB5EM2PB8")] + [InlineData("abcdefghijklmnopqrstuvwxyz", "C5H66P35CPJMGQBADDM6QRJFE1ON4SRKELR7EU3PF8")] + [InlineData("0123456789", "60OJ4CPK6KR3EE1P")] + public void Base32CodecEncodeShouldProduceExpectedResultBase32Hex(string value, string expected) + { + // Given + IBaseCodec codec = IBaseCodec.Base32; + byte[] bytes = value.ToByteArray(); + + // When + string actual = codec.Encode(bytes, Base32FormatProvider.Base32Hex); + + // Then + Assert.Equal(expected, actual); + } + + [Theory(DisplayName = "Base32Codec.Decode should produce the expected result (Base32Hex)")] + [InlineData("", "")] + [InlineData("85146H258P3KGIAA9D64QJIFA18L4KQKALB5EM2PB8", "ABCDEFGHIJKLMNOPQRSTUVWXYZ")] + [InlineData("C5H66P35CPJMGQBADDM6QRJFE1ON4SRKELR7EU3PF8", "abcdefghijklmnopqrstuvwxyz")] + [InlineData("60OJ4CPK6KR3EE1P", "0123456789")] + public void Base32CodecDecodeShouldProduceExpectedResultBase32Hex(string value, string expected) + { + // Given + IBaseCodec codec = IBaseCodec.Base32; + + // When + byte[] bytes = codec.Decode(value, Base32FormatProvider.Base32Hex); + string actual = Encoding.UTF8.GetString(bytes); + + // Then + Assert.Equal(expected, actual); + } + + [Theory(DisplayName = "Base32Codec.Encode should produce the expected result (Crockford)")] + [InlineData("", "")] + [InlineData("ABCDEFGHIJKLMNOPQRSTUVWXYZ", "85146H258S3MGJAA9D64TKJFA18N4MTMANB5EP2SB8")] + [InlineData("abcdefghijklmnopqrstuvwxyz", "C5H66S35CSKPGTBADDP6TVKFE1RQ4WVMENV7EY3SF8")] + [InlineData("0123456789", "60RK4CSM6MV3EE1S")] + public void Base32CodecEncodeShouldProduceExpectedResultCrockford(string value, string expected) + { + // Given + IBaseCodec codec = IBaseCodec.Base32; + byte[] bytes = value.ToByteArray(); + + // When + string actual = codec.Encode(bytes, Base32FormatProvider.Crockford); + + // Then + Assert.Equal(expected, actual); + } + + [Theory(DisplayName = "Base32Codec.Decode should produce the expected result (Crockford)")] + [InlineData("", "")] + [InlineData("85146H258S3MGJAA9D64TKJFA18N4MTMANB5EP2SB8", "ABCDEFGHIJKLMNOPQRSTUVWXYZ")] + [InlineData("C5H66S35CSKPGTBADDP6TVKFE1RQ4WVMENV7EY3SF8", "abcdefghijklmnopqrstuvwxyz")] + [InlineData("60RK4CSM6MV3EE1S", "0123456789")] + public void Base32CodecDecodeShouldProduceExpectedResultCrockford(string value, string expected) + { + // Given + IBaseCodec codec = IBaseCodec.Base32; + + // When + byte[] bytes = codec.Decode(value, Base32FormatProvider.Crockford); + string actual = Encoding.UTF8.GetString(bytes); + + // Then + Assert.Equal(expected, actual); + } + + [Theory(DisplayName = "Base32Codec.Encode should produce the expected result (GeoHash)")] + [InlineData("", "")] + [InlineData("ABCDEFGHIJKLMNOPQRSTUVWXYZ", "85146j258t3nhkbb9e64umkgb18p4nunbpc5fq2tc8")] + [InlineData("abcdefghijklmnopqrstuvwxyz", "d5j66t35dtmqhucbeeq6uvmgf1sr4wvnfpv7fy3tg8")] + [InlineData("0123456789", "60sm4dtn6nv3ff1t")] + public void Base32CodecEncodeShouldProduceExpectedResultGeoHash(string value, string expected) + { + // Given + IBaseCodec codec = IBaseCodec.Base32; + byte[] bytes = value.ToByteArray(); + + // When + string actual = codec.Encode(bytes, Base32FormatProvider.GeoHash); + + // Then + Assert.Equal(expected, actual); + } + + [Theory(DisplayName = "Base32Codec.Decode should produce the expected result (GeoHash)")] + [InlineData("", "")] + [InlineData("85146j258t3nhkbb9e64umkgb18p4nunbpc5fq2tc8", "ABCDEFGHIJKLMNOPQRSTUVWXYZ")] + [InlineData("d5j66t35dtmqhucbeeq6uvmgf1sr4wvnfpv7fy3tg8", "abcdefghijklmnopqrstuvwxyz")] + [InlineData("60sm4dtn6nv3ff1t", "0123456789")] + public void Base32CodecDecodeShouldProduceExpectedResultGeoHash(string value, string expected) + { + // Given + IBaseCodec codec = IBaseCodec.Base32; + + // When + byte[] bytes = codec.Decode(value, Base32FormatProvider.GeoHash); + string actual = Encoding.UTF8.GetString(bytes); + + // Then + Assert.Equal(expected, actual); + } + + [Theory(DisplayName = "Base32Codec.Encode should produce the expected result (ZBase32)")] + [InlineData("", "")] + [InlineData("ABCDEFGHIJKLMNOPQRSTUVWXYZ", "efbrgtnfe3dwo1kkjpgr4u1xkbeirw4wkimfqsn3me")] + [InlineData("abcdefghijklmnopqrstuvwxyz", "cftgg3dfc3uso4mkppsg45uxqbazrh5wqi58q6d3xe")] + [InlineData("0123456789", "gyaurc3wgw5dqqb3")] + public void Base32CodecEncodeShouldProduceExpectedResultZBase32(string value, string expected) + { + // Given + IBaseCodec codec = IBaseCodec.Base32; + byte[] bytes = value.ToByteArray(); + + // When + string actual = codec.Encode(bytes, Base32FormatProvider.ZBase32); + + // Then + Assert.Equal(expected, actual); + } + + [Theory(DisplayName = "Base32Codec.Decode should produce the expected result (ZBase32)")] + [InlineData("", "")] + [InlineData("efbrgtnfe3dwo1kkjpgr4u1xkbeirw4wkimfqsn3me", "ABCDEFGHIJKLMNOPQRSTUVWXYZ")] + [InlineData("cftgg3dfc3uso4mkppsg45uxqbazrh5wqi58q6d3xe", "abcdefghijklmnopqrstuvwxyz")] + [InlineData("gyaurc3wgw5dqqb3", "0123456789")] + public void Base32CodecDecodeShouldProduceExpectedResultZBase32(string value, string expected) + { + // Given + IBaseCodec codec = IBaseCodec.Base32; + + // When + byte[] bytes = codec.Decode(value, Base32FormatProvider.ZBase32); + string actual = Encoding.UTF8.GetString(bytes); + + // Then + Assert.Equal(expected, actual); + } + + [Theory(DisplayName = "Base32Codec.Encode should produce the expected result (PaddedRfc4648)")] + [InlineData("", "")] + [InlineData("ABCDEFGHIJKLMNOPQRSTUVWXYZ", "IFBEGRCFIZDUQSKKJNGE2TSPKBIVEU2UKVLFOWCZLI======")] + [InlineData("abcdefghijklmnopqrstuvwxyz", "MFRGGZDFMZTWQ2LKNNWG23TPOBYXE43UOV3HO6DZPI======")] + [InlineData("0123456789", "GAYTEMZUGU3DOOBZ")] + public void Base32CodecEncodeShouldProduceExpectedResultPaddedRfc4648(string value, string expected) + { + // Given + IBaseCodec codec = IBaseCodec.Base32; + byte[] bytes = value.ToByteArray(); + + // When + string actual = codec.Encode(bytes, Base32FormatProvider.PaddedRfc4648); + + // Then + Assert.Equal(expected, actual); + } + + [Theory(DisplayName = "Base32Codec.Decode should produce the expected result (PaddedRfc4648)")] + [InlineData("", "")] + [InlineData("IFBEGRCFIZDUQSKKJNGE2TSPKBIVEU2UKVLFOWCZLI======", "ABCDEFGHIJKLMNOPQRSTUVWXYZ")] + [InlineData("MFRGGZDFMZTWQ2LKNNWG23TPOBYXE43UOV3HO6DZPI======", "abcdefghijklmnopqrstuvwxyz")] + [InlineData("GAYTEMZUGU3DOOBZ", "0123456789")] + public void Base32CodecDecodeShouldProduceExpectedResultPaddedRfc4648(string value, string expected) + { + // Given + IBaseCodec codec = IBaseCodec.Base32; + + // When + byte[] bytes = codec.Decode(value, Base32FormatProvider.PaddedRfc4648); + string actual = Encoding.UTF8.GetString(bytes); + + // Then + Assert.Equal(expected, actual); + } + + [Theory(DisplayName = "Base32Codec.Encode should produce the expected result (PaddedBase32Hex)")] + [InlineData("", "")] + [InlineData("ABCDEFGHIJKLMNOPQRSTUVWXYZ", "85146H258P3KGIAA9D64QJIFA18L4KQKALB5EM2PB8======")] + [InlineData("abcdefghijklmnopqrstuvwxyz", "C5H66P35CPJMGQBADDM6QRJFE1ON4SRKELR7EU3PF8======")] + [InlineData("0123456789", "60OJ4CPK6KR3EE1P")] + public void Base32CodecEncodeShouldProduceExpectedResultPaddedBase32Hex(string value, string expected) + { + // Given + IBaseCodec codec = IBaseCodec.Base32; + byte[] bytes = value.ToByteArray(); + + // When + string actual = codec.Encode(bytes, Base32FormatProvider.PaddedBase32Hex); + + // Then + Assert.Equal(expected, actual); + } + + [Theory(DisplayName = "Base32Codec.Decode should produce the expected result (PaddedBase32Hex)")] + [InlineData("", "")] + [InlineData("85146H258P3KGIAA9D64QJIFA18L4KQKALB5EM2PB8======", "ABCDEFGHIJKLMNOPQRSTUVWXYZ")] + [InlineData("C5H66P35CPJMGQBADDM6QRJFE1ON4SRKELR7EU3PF8======", "abcdefghijklmnopqrstuvwxyz")] + [InlineData("60OJ4CPK6KR3EE1P", "0123456789")] + public void Base32CodecDecodeShouldProduceExpectedResultPaddedBase32Hex(string value, string expected) + { + // Given + IBaseCodec codec = IBaseCodec.Base32; + + // When + byte[] bytes = codec.Decode(value, Base32FormatProvider.PaddedBase32Hex); + string actual = Encoding.UTF8.GetString(bytes); + + // Then + Assert.Equal(expected, actual); + } + + [Theory(DisplayName = "Base32Codec.Encode should produce the expected result (PaddedCrockford)")] + [InlineData("", "")] + [InlineData("ABCDEFGHIJKLMNOPQRSTUVWXYZ", "85146H258S3MGJAA9D64TKJFA18N4MTMANB5EP2SB8======")] + [InlineData("abcdefghijklmnopqrstuvwxyz", "C5H66S35CSKPGTBADDP6TVKFE1RQ4WVMENV7EY3SF8======")] + [InlineData("0123456789", "60RK4CSM6MV3EE1S")] + public void Base32CodecEncodeShouldProduceExpectedResultPaddedCrockford(string value, string expected) + { + // Given + IBaseCodec codec = IBaseCodec.Base32; + byte[] bytes = value.ToByteArray(); + + // When + string actual = codec.Encode(bytes, Base32FormatProvider.PaddedCrockford); + + // Then + Assert.Equal(expected, actual); + } + + [Theory(DisplayName = "Base32Codec.Decode should produce the expected result (PaddedCrockford)")] + [InlineData("", "")] + [InlineData("85146H258S3MGJAA9D64TKJFA18N4MTMANB5EP2SB8======", "ABCDEFGHIJKLMNOPQRSTUVWXYZ")] + [InlineData("C5H66S35CSKPGTBADDP6TVKFE1RQ4WVMENV7EY3SF8======", "abcdefghijklmnopqrstuvwxyz")] + [InlineData("60RK4CSM6MV3EE1S", "0123456789")] + public void Base32CodecDecodeShouldProduceExpectedResultPaddedCrockford(string value, string expected) + { + // Given + IBaseCodec codec = IBaseCodec.Base32; + + // When + byte[] bytes = codec.Decode(value, Base32FormatProvider.PaddedCrockford); + string actual = Encoding.UTF8.GetString(bytes); + + // Then + Assert.Equal(expected, actual); + } + + [Theory(DisplayName = "Base32Codec.Encode should produce the expected result (PaddedGeoHash)")] + [InlineData("", "")] + [InlineData("ABCDEFGHIJKLMNOPQRSTUVWXYZ", "85146j258t3nhkbb9e64umkgb18p4nunbpc5fq2tc8======")] + [InlineData("abcdefghijklmnopqrstuvwxyz", "d5j66t35dtmqhucbeeq6uvmgf1sr4wvnfpv7fy3tg8======")] + [InlineData("0123456789", "60sm4dtn6nv3ff1t")] + public void Base32CodecEncodeShouldProduceExpectedResultPaddedGeoHash(string value, string expected) + { + // Given + IBaseCodec codec = IBaseCodec.Base32; + byte[] bytes = value.ToByteArray(); + + // When + string actual = codec.Encode(bytes, Base32FormatProvider.PaddedGeoHash); + + // Then + Assert.Equal(expected, actual); + } + + [Theory(DisplayName = "Base32Codec.Decode should produce the expected result (PaddedGeoHash)")] + [InlineData("", "")] + [InlineData("85146j258t3nhkbb9e64umkgb18p4nunbpc5fq2tc8======", "ABCDEFGHIJKLMNOPQRSTUVWXYZ")] + [InlineData("d5j66t35dtmqhucbeeq6uvmgf1sr4wvnfpv7fy3tg8======", "abcdefghijklmnopqrstuvwxyz")] + [InlineData("60sm4dtn6nv3ff1t", "0123456789")] + public void Base32CodecDecodeShouldProduceExpectedResultPaddedGeoHash(string value, string expected) + { + // Given + IBaseCodec codec = IBaseCodec.Base32; + + // When + byte[] bytes = codec.Decode(value, Base32FormatProvider.PaddedGeoHash); + string actual = Encoding.UTF8.GetString(bytes); + + // Then + Assert.Equal(expected, actual); + } + + [Theory(DisplayName = "Base32Codec.Encode should produce the expected result (PaddedZBase32)")] + [InlineData("", "")] + [InlineData("ABCDEFGHIJKLMNOPQRSTUVWXYZ", "efbrgtnfe3dwo1kkjpgr4u1xkbeirw4wkimfqsn3me======")] + [InlineData("abcdefghijklmnopqrstuvwxyz", "cftgg3dfc3uso4mkppsg45uxqbazrh5wqi58q6d3xe======")] + [InlineData("0123456789", "gyaurc3wgw5dqqb3")] + public void Base32CodecEncodeShouldProduceExpectedResultPaddedZBase32(string value, string expected) + { + // Given + IBaseCodec codec = IBaseCodec.Base32; + byte[] bytes = value.ToByteArray(); + + // When + string actual = codec.Encode(bytes, Base32FormatProvider.PaddedZBase32); + + // Then + Assert.Equal(expected, actual); + } + + [Theory(DisplayName = "Base32Codec.Decode should produce the expected result (PaddedZBase32)")] + [InlineData("", "")] + [InlineData("efbrgtnfe3dwo1kkjpgr4u1xkbeirw4wkimfqsn3me======", "ABCDEFGHIJKLMNOPQRSTUVWXYZ")] + [InlineData("cftgg3dfc3uso4mkppsg45uxqbazrh5wqi58q6d3xe======", "abcdefghijklmnopqrstuvwxyz")] + [InlineData("gyaurc3wgw5dqqb3", "0123456789")] + public void Base32CodecDecodeShouldProduceExpectedResultPaddedZBase32(string value, string expected) + { + // Given + IBaseCodec codec = IBaseCodec.Base32; + + // When + byte[] bytes = codec.Decode(value, Base32FormatProvider.PaddedZBase32); + string actual = Encoding.UTF8.GetString(bytes); + + // Then + Assert.Equal(expected, actual); + } + + [Fact(DisplayName = "Base32Codec.Encode should throw FormatException when the format provider is invalid")] + public void Base32CodecEncodeShouldThrowFormatExceptionWhenFormatProviderIsInvalid() + { + // Given + IBaseCodec codec = IBaseCodec.Base32; + byte[] bytes = "Hello, World!".ToByteArray(); + + // When + Exception exception = Assert.Throws(() => codec.Encode(bytes, InvalidFormatProvider.Instance)); + + // Then + Assert.Equal("Encoding operation failed due to an invalid value or format provider.", exception.Message); + } + + [Fact(DisplayName = "Base32Codec.Decode should throw FormatException when the format provider is invalid")] + public void Base32CodecDecodeShouldThrowFormatExceptionWhenTheFormatProviderIsInvalid() + { + // Given + IBaseCodec codec = IBaseCodec.Base32; + const string value = "IFBEGRCFIZDUQSKKJNGE2TSPKBIVEU2UKVLFOWCZLI"; + + // When + Exception exception = Assert.Throws(() => codec.Decode(value, InvalidFormatProvider.Instance)); + + // Then + Assert.Equal("Decoding operation failed due to an invalid value or format provider.", exception.Message); + } + + [Fact(DisplayName = "Base32Codec.Decode should throw FormatException when the value is invalid")] + public void Base32CodecDecodeShouldThrowFormatExceptionWhenTheValueIsInvalid() + { + // Given + IBaseCodec codec = IBaseCodec.Base32; + const string value = "*INVALID_VALUE*"; + + // When + Exception exception = Assert.Throws(() => codec.Decode(value)); + + // Then + Assert.Equal("Decoding operation failed due to an invalid value or format provider.", exception.Message); + } + + [Fact(DisplayName = "Base32Codec.TryEncode should return false when the format provider is invalid")] + public void Base32CodecTryEncodeShouldReturnFalseWhenValueIsInvalid() + { + // Given + IBaseCodec codec = IBaseCodec.Base32; + byte[] bytes = "Hello, World!".ToByteArray(); + + // When + bool result = codec.TryEncode(bytes, InvalidFormatProvider.Instance, out string _); + + // Then + Assert.False(result); + } + + [Fact(DisplayName = "Base32Codec.TryEncode should return false when the value is invalid")] + public void Base32CodecTryEncodeShouldReturnFalseWhenTheValueIsInvalid() + { + // Given + IBaseCodec codec = IBaseCodec.Base32; + byte[] bytes = new byte[int.MaxValue / 4]; + + // When + bool result = codec.TryEncode(bytes, Base32FormatProvider.Rfc4648, out string _); + + // Then + Assert.False(result); + } + + [Fact(DisplayName = "Base32Codec.TryDecode should return false when trying to decode a non-padded value with a padded format provider")] + public void Base32CodecTryDecodeShouldReturnFalseWhenTryingToDecodeANonPaddedValueWithAPaddedFormatProvider() + { + // Given + IBaseCodec codec = IBaseCodec.Base32; + + // When + bool result = codec.TryDecode("IFBEGRCFIZDUQSKKJNGE2TSPKBIVEU2UKVLFOWCZLI", Base32FormatProvider.PaddedRfc4648, out byte[] _); + + // Then + Assert.False(result); + } + + [Fact(DisplayName = "Base32Codec.TryDecode should return false when trying to decode a sequence of padding charachers")] + public void Base32CodecTryDecodeShouldReturnFalseWhenTryingToDecodeASequenceOfPaddingCharacters() + { + // Given + IBaseCodec codec = IBaseCodec.Base32; + + // When + bool result = codec.TryDecode("========", Base32FormatProvider.PaddedRfc4648, out byte[] _); + + // Then + Assert.False(result); + } + + [Fact(DisplayName = "Base32Codec.TryDecode should return false when the value is invalid")] + public void Base32CodecTryDecodeShouldReturnFalseWhenTheValueIsInvalid() + { + // Given + IBaseCodec codec = IBaseCodec.Base32; + string value = new('A', int.MaxValue / 4); + + // When + bool result = codec.TryDecode(value, Base32FormatProvider.Rfc4648, out byte[] _); + + // Then + Assert.False(result); + } +} diff --git a/OnixLabs.Core.UnitTests/Text/Base32CodecZBase32Tests.cs b/OnixLabs.Core.UnitTests/Text/Base32CodecZBase32Tests.cs deleted file mode 100644 index 23d1832..0000000 --- a/OnixLabs.Core.UnitTests/Text/Base32CodecZBase32Tests.cs +++ /dev/null @@ -1,58 +0,0 @@ -// Copyright 2020 ONIXLabs -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -using System.Text; -using OnixLabs.Core.Text; -using Xunit; - -namespace OnixLabs.Core.UnitTests.Text; - -public sealed class Base32CodecZBase32Tests -{ - [Theory(DisplayName = "Base32Codec.Encode should produce the expected result")] - [InlineData("", "")] - [InlineData("ABCDEFGHIJKLMNOPQRSTUVWXYZ","efbrgtnfe3dwo1kkjpgr4u1xkbeirw4wkimfqsn3me")] - [InlineData("abcdefghijklmnopqrstuvwxyz","cftgg3dfc3uso4mkppsg45uxqbazrh5wqi58q6d3xe")] - [InlineData("0123456789","gyaurc3wgw5dqqb3")] - public void Base32CodecEncodeShouldProduceExpectedResult(string value, string expected) - { - // Given - IBaseCodec codec = IBaseCodec.Base32; - byte[] bytes = value.ToByteArray(); - - // When - string actual = codec.Encode(bytes, Base32FormatProvider.ZBase32); - - // Then - Assert.Equal(expected, actual); - } - - [Theory(DisplayName = "Base32Codec.Decode should produce the expected result")] - [InlineData("", "")] - [InlineData("efbrgtnfe3dwo1kkjpgr4u1xkbeirw4wkimfqsn3me","ABCDEFGHIJKLMNOPQRSTUVWXYZ")] - [InlineData("cftgg3dfc3uso4mkppsg45uxqbazrh5wqi58q6d3xe","abcdefghijklmnopqrstuvwxyz")] - [InlineData("gyaurc3wgw5dqqb3","0123456789")] - public void Base32CodecDecodeShouldProduceExpectedResult(string value, string expected) - { - // Given - IBaseCodec codec = IBaseCodec.Base32; - - // When - byte[] bytes = codec.Decode(value, Base32FormatProvider.ZBase32); - string actual = Encoding.UTF8.GetString(bytes); - - // Then - Assert.Equal(expected, actual); - } -} diff --git a/OnixLabs.Core.UnitTests/Text/Base32FormatProviderTests.cs b/OnixLabs.Core.UnitTests/Text/Base32FormatProviderTests.cs new file mode 100644 index 0000000..8ab5839 --- /dev/null +++ b/OnixLabs.Core.UnitTests/Text/Base32FormatProviderTests.cs @@ -0,0 +1,269 @@ +// Copyright 2020 ONIXLabs +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +using System; +using OnixLabs.Core.Text; +using Xunit; + +namespace OnixLabs.Core.UnitTests.Text; + +public sealed class Base32FormatProviderTests +{ + [Fact(DisplayName = "Base32FormatProvider.Rfc4648 should produce the expected result")] + public void Base32FormatProviderRfc4648ShouldProduceExpectedAlphabet() + { + // Given + const int expectedValue = 0; + const string expectedName = "Rfc4648"; + const string expectedAlphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567"; + const bool expectedPadding = false; + + // When + int actualValue = Base32FormatProvider.Rfc4648.Value; + string actualName = Base32FormatProvider.Rfc4648.Name; + string actualAlphabet = Base32FormatProvider.Rfc4648.Alphabet; + bool actualPadding = Base32FormatProvider.Rfc4648.IsPadded; + + // Then + Assert.Equal(expectedValue, actualValue); + Assert.Equal(expectedName, actualName); + Assert.Equal(expectedAlphabet, actualAlphabet); + Assert.Equal(expectedPadding, actualPadding); + } + + [Fact(DisplayName = "Base32FormatProvider.ZBase32 should produce the expected result")] + public void Base32FormatProviderZBase32ShouldProduceExpectedAlphabet() + { + // Given + const int expectedValue = 1; + const string expectedName = "ZBase32"; + const string expectedAlphabet = "ybndrfg8ejkmcpqxot1uwisza345h769"; + const bool expectedPadding = false; + + // When + int actualValue = Base32FormatProvider.ZBase32.Value; + string actualName = Base32FormatProvider.ZBase32.Name; + string actualAlphabet = Base32FormatProvider.ZBase32.Alphabet; + bool actualPadding = Base32FormatProvider.ZBase32.IsPadded; + + // Then + Assert.Equal(expectedValue, actualValue); + Assert.Equal(expectedName, actualName); + Assert.Equal(expectedAlphabet, actualAlphabet); + Assert.Equal(expectedPadding, actualPadding); + } + + [Fact(DisplayName = "Base32FormatProvider.GeoHash should produce the expected result")] + public void Base32FormatProviderGeoHashShouldProduceExpectedAlphabet() + { + // Given + const int expectedValue = 2; + const string expectedName = "GeoHash"; + const string expectedAlphabet = "0123456789bcdefghjkmnpqrstuvwxyz"; + const bool expectedPadding = false; + + // When + int actualValue = Base32FormatProvider.GeoHash.Value; + string actualName = Base32FormatProvider.GeoHash.Name; + string actualAlphabet = Base32FormatProvider.GeoHash.Alphabet; + bool actualPadding = Base32FormatProvider.GeoHash.IsPadded; + + // Then + Assert.Equal(expectedValue, actualValue); + Assert.Equal(expectedName, actualName); + Assert.Equal(expectedAlphabet, actualAlphabet); + Assert.Equal(expectedPadding, actualPadding); + } + + [Fact(DisplayName = "Base32FormatProvider.Crockford should produce the expected result")] + public void Base32FormatProviderCrockfordShouldProduceExpectedAlphabet() + { + // Given + const int expectedValue = 3; + const string expectedName = "Crockford"; + const string expectedAlphabet = "0123456789ABCDEFGHJKMNPQRSTVWXYZ"; + const bool expectedPadding = false; + + // When + int actualValue = Base32FormatProvider.Crockford.Value; + string actualName = Base32FormatProvider.Crockford.Name; + string actualAlphabet = Base32FormatProvider.Crockford.Alphabet; + bool actualPadding = Base32FormatProvider.Crockford.IsPadded; + + // Then + Assert.Equal(expectedValue, actualValue); + Assert.Equal(expectedName, actualName); + Assert.Equal(expectedAlphabet, actualAlphabet); + Assert.Equal(expectedPadding, actualPadding); + } + + [Fact(DisplayName = "Base32FormatProvider.Base32Hex should produce the expected result")] + public void Base32FormatProviderBase32HexShouldProduceExpectedAlphabet() + { + // Given + const int expectedValue = 4; + const string expectedName = "Base32Hex"; + const string expectedAlphabet = "0123456789ABCDEFGHIJKLMNOPQRSTUV"; + const bool expectedPadding = false; + + // When + int actualValue = Base32FormatProvider.Base32Hex.Value; + string actualName = Base32FormatProvider.Base32Hex.Name; + string actualAlphabet = Base32FormatProvider.Base32Hex.Alphabet; + bool actualPadding = Base32FormatProvider.Base32Hex.IsPadded; + + // Then + Assert.Equal(expectedValue, actualValue); + Assert.Equal(expectedName, actualName); + Assert.Equal(expectedAlphabet, actualAlphabet); + Assert.Equal(expectedPadding, actualPadding); + } + + [Fact(DisplayName = "Base32FormatProvider.PaddedRfc4648 should produce the expected result")] + public void Base32FormatProviderPaddedRfc4648ShouldProduceExpectedAlphabet() + { + // Given + const int expectedValue = 5; + const string expectedName = "PaddedRfc4648"; + const string expectedAlphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567"; + const bool expectedPadding = true; + + // When + int actualValue = Base32FormatProvider.PaddedRfc4648.Value; + string actualName = Base32FormatProvider.PaddedRfc4648.Name; + string actualAlphabet = Base32FormatProvider.PaddedRfc4648.Alphabet; + bool actualPadding = Base32FormatProvider.PaddedRfc4648.IsPadded; + + // Then + Assert.Equal(expectedValue, actualValue); + Assert.Equal(expectedName, actualName); + Assert.Equal(expectedAlphabet, actualAlphabet); + Assert.Equal(expectedPadding, actualPadding); + } + + [Fact(DisplayName = "Base32FormatProvider.PaddedZBase32 should produce the expected result")] + public void Base32FormatProviderPaddedZBase32ShouldProduceExpectedAlphabet() + { + // Given + const int expectedValue = 6; + const string expectedName = "PaddedZBase32"; + const string expectedAlphabet = "ybndrfg8ejkmcpqxot1uwisza345h769"; + const bool expectedPadding = true; + + // When + int actualValue = Base32FormatProvider.PaddedZBase32.Value; + string actualName = Base32FormatProvider.PaddedZBase32.Name; + string actualAlphabet = Base32FormatProvider.PaddedZBase32.Alphabet; + bool actualPadding = Base32FormatProvider.PaddedZBase32.IsPadded; + + // Then + Assert.Equal(expectedValue, actualValue); + Assert.Equal(expectedName, actualName); + Assert.Equal(expectedAlphabet, actualAlphabet); + Assert.Equal(expectedPadding, actualPadding); + } + + [Fact(DisplayName = "Base32FormatProvider.PaddedGeoHash should produce the expected result")] + public void Base32FormatProviderPaddedGeoHashShouldProduceExpectedAlphabet() + { + // Given + const int expectedValue = 7; + const string expectedName = "PaddedGeoHash"; + const string expectedAlphabet = "0123456789bcdefghjkmnpqrstuvwxyz"; + const bool expectedPadding = true; + + // When + int actualValue = Base32FormatProvider.PaddedGeoHash.Value; + string actualName = Base32FormatProvider.PaddedGeoHash.Name; + string actualAlphabet = Base32FormatProvider.PaddedGeoHash.Alphabet; + bool actualPadding = Base32FormatProvider.PaddedGeoHash.IsPadded; + + // Then + Assert.Equal(expectedValue, actualValue); + Assert.Equal(expectedName, actualName); + Assert.Equal(expectedAlphabet, actualAlphabet); + Assert.Equal(expectedPadding, actualPadding); + } + + [Fact(DisplayName = "Base32FormatProvider.PaddedCrockford should produce the expected result")] + public void Base32FormatProviderPaddedCrockfordShouldProduceExpectedAlphabet() + { + // Given + const int expectedValue = 8; + const string expectedName = "PaddedCrockford"; + const string expectedAlphabet = "0123456789ABCDEFGHJKMNPQRSTVWXYZ"; + const bool expectedPadding = true; + + // When + int actualValue = Base32FormatProvider.PaddedCrockford.Value; + string actualName = Base32FormatProvider.PaddedCrockford.Name; + string actualAlphabet = Base32FormatProvider.PaddedCrockford.Alphabet; + bool actualPadding = Base32FormatProvider.PaddedCrockford.IsPadded; + + // Then + Assert.Equal(expectedValue, actualValue); + Assert.Equal(expectedName, actualName); + Assert.Equal(expectedAlphabet, actualAlphabet); + Assert.Equal(expectedPadding, actualPadding); + } + + [Fact(DisplayName = "Base32FormatProvider.PaddedBase32Hex should produce the expected result")] + public void Base32FormatProviderPaddedBase32HexShouldProduceExpectedAlphabet() + { + // Given + const int expectedValue = 9; + const string expectedName = "PaddedBase32Hex"; + const string expectedAlphabet = "0123456789ABCDEFGHIJKLMNOPQRSTUV"; + const bool expectedPadding = true; + + // When + int actualValue = Base32FormatProvider.PaddedBase32Hex.Value; + string actualName = Base32FormatProvider.PaddedBase32Hex.Name; + string actualAlphabet = Base32FormatProvider.PaddedBase32Hex.Alphabet; + bool actualPadding = Base32FormatProvider.PaddedBase32Hex.IsPadded; + + // Then + Assert.Equal(expectedValue, actualValue); + Assert.Equal(expectedName, actualName); + Assert.Equal(expectedAlphabet, actualAlphabet); + Assert.Equal(expectedPadding, actualPadding); + } + + [Fact(DisplayName = "Base32FormatProvider.GetFormat should return Base32FormatProvider when the given type is Base32FormatProvider")] + public void Base32FormatProviderShouldReturnBase32FormatProviderWhenGivenTypeIsBase32FormatProvider() + { + // Given + Type type = typeof(Base32FormatProvider); + + // When + object? result = Base32FormatProvider.Rfc4648.GetFormat(type); + + // Then + Assert.NotNull(result); + Assert.IsType(result); + } + + [Fact(DisplayName = "Base32FormatProvider.GetFormat should return null when the given type is not Base32FormatProvider")] + public void Base32FormatProviderShouldReturnNullWhenGivenTypeIsNotBase32FormatProvider() + { + // Given + Type type = typeof(object); + + // When + object? result = Base32FormatProvider.Rfc4648.GetFormat(type); + + // Then + Assert.Null(result); + } +} diff --git a/OnixLabs.Core.UnitTests/Text/Base32Tests.cs b/OnixLabs.Core.UnitTests/Text/Base32Tests.cs index 2258ffa..527a575 100644 --- a/OnixLabs.Core.UnitTests/Text/Base32Tests.cs +++ b/OnixLabs.Core.UnitTests/Text/Base32Tests.cs @@ -12,6 +12,8 @@ // See the License for the specific language governing permissions and // limitations under the License. +using System; +using System.Buffers; using System.Text; using OnixLabs.Core.Text; using Xunit; @@ -20,6 +22,81 @@ namespace OnixLabs.Core.UnitTests.Text; public sealed class Base32Tests { + [Fact(DisplayName = "Base32 should be implicitly constructable from byte[]")] + public void Base32ShouldBeImplicitlyConstructableFromByteArray() + { + // Given + const string expected = "IFBEGYLCMMYTEMY"; + byte[] value = "ABCabc123".ToByteArray(); + + // When + Base32 candidate = value; + string actual = candidate.ToString(); + + // Then + Assert.Equal(expected, actual); + } + + [Fact(DisplayName = "Base32 should be implicitly constructable from ReadOnlySpan")] + public void Base32ShouldBeImplicitlyConstructableFromReadOnlySpanOfByte() + { + // Given + const string expected = "IFBEGYLCMMYTEMY"; + ReadOnlySpan value = "ABCabc123".ToByteArray().AsSpan(); + + // When + Base32 candidate = value; + string actual = candidate.ToString(); + + // Then + Assert.Equal(expected, actual); + } + + [Fact(DisplayName = "Base32 should be implicitly constructable from string")] + public void Base32ShouldBeImplicitlyConstructableFromString() + { + // Given + const string expected = "IFBEGYLCMMYTEMY"; + const string value = "ABCabc123"; + + // When + Base32 candidate = value; + string actual = candidate.ToString(); + + // Then + Assert.Equal(expected, actual); + } + + [Fact(DisplayName = "Base32 should be implicitly constructable from char[]")] + public void Base32ShouldBeImplicitlyConstructableFromCharArray() + { + // Given + const string expected = "IFBEGYLCMMYTEMY"; + char[] value = "ABCabc123".ToCharArray(); + + // When + Base32 candidate = value; + string actual = candidate.ToString(); + + // Then + Assert.Equal(expected, actual); + } + + [Fact(DisplayName = "Base32 should be implicitly constructable from ReadOnlySequence")] + public void Base32ShouldBeImplicitlyConstructableFromReadOnlySequenceOfChar() + { + // Given + const string expected = "IFBEGYLCMMYTEMY"; + ReadOnlySequence value = new("ABCabc123".ToCharArray()); + + // When + Base32 candidate = value; + string actual = candidate.ToString(); + + // Then + Assert.Equal(expected, actual); + } + [Fact(DisplayName = "Base32 should not change when modifying the original byte array")] public void Base32ShouldNotChangeWhenModifyingOriginalByteArray() { @@ -99,9 +176,9 @@ public void Base32ValuesShouldNotBeIdentical() [Theory(DisplayName = "Base32.Parse should produce the expected result")] [InlineData("", "")] - [InlineData("IFBEGRCFIZDUQSKKJNGE2TSPKBIVEU2UKVLFOWCZLI","ABCDEFGHIJKLMNOPQRSTUVWXYZ")] - [InlineData("MFRGGZDFMZTWQ2LKNNWG23TPOBYXE43UOV3HO6DZPI","abcdefghijklmnopqrstuvwxyz")] - [InlineData("GAYTEMZUGU3DOOBZ","0123456789")] + [InlineData("IFBEGRCFIZDUQSKKJNGE2TSPKBIVEU2UKVLFOWCZLI", "ABCDEFGHIJKLMNOPQRSTUVWXYZ")] + [InlineData("MFRGGZDFMZTWQ2LKNNWG23TPOBYXE43UOV3HO6DZPI", "abcdefghijklmnopqrstuvwxyz")] + [InlineData("GAYTEMZUGU3DOOBZ", "0123456789")] public void Base32ParseShouldProduceExpectedResult(string value, string expected) { // Given @@ -116,9 +193,9 @@ public void Base32ParseShouldProduceExpectedResult(string value, string expected [Theory(DisplayName = "Base32.ToString should produce the expected result")] [InlineData("", "")] - [InlineData("ABCDEFGHIJKLMNOPQRSTUVWXYZ","IFBEGRCFIZDUQSKKJNGE2TSPKBIVEU2UKVLFOWCZLI")] - [InlineData("abcdefghijklmnopqrstuvwxyz","MFRGGZDFMZTWQ2LKNNWG23TPOBYXE43UOV3HO6DZPI")] - [InlineData("0123456789","GAYTEMZUGU3DOOBZ")] + [InlineData("ABCDEFGHIJKLMNOPQRSTUVWXYZ", "IFBEGRCFIZDUQSKKJNGE2TSPKBIVEU2UKVLFOWCZLI")] + [InlineData("abcdefghijklmnopqrstuvwxyz", "MFRGGZDFMZTWQ2LKNNWG23TPOBYXE43UOV3HO6DZPI")] + [InlineData("0123456789", "GAYTEMZUGU3DOOBZ")] public void Base32ToStringShouldProduceExpectedResult(string value, string expected) { // Given @@ -131,4 +208,105 @@ public void Base32ToStringShouldProduceExpectedResult(string value, string expec // Then Assert.Equal(expected, actual); } + + [Theory(DisplayName = "Base32.TryFormat should produce the expected result when the value was formatted correctly")] + [InlineData("", "")] + [InlineData("ABCDEFGHIJKLMNOPQRSTUVWXYZ", "IFBEGRCFIZDUQSKKJNGE2TSPKBIVEU2UKVLFOWCZLI")] + [InlineData("abcdefghijklmnopqrstuvwxyz", "MFRGGZDFMZTWQ2LKNNWG23TPOBYXE43UOV3HO6DZPI")] + [InlineData("0123456789", "GAYTEMZUGU3DOOBZ")] + public void Base32TryFormatShouldProduceExpectedResultWhenValueWasFormattedCorrectly(string value, string expected) + { + // Given + ISpanFormattable candidate = new Base32(Encoding.UTF8.GetBytes(value)); + Span destination = stackalloc char[expected.Length]; + + // When + bool result = candidate.TryFormat(destination, out int charsWritten, [], null); + string actual = destination.ToString(); + + // Then + Assert.True(result); + Assert.Equal(expected, actual); + Assert.Equal(expected.Length, charsWritten); + } + + [Theory(DisplayName = "Base32.TryFormat should produce the expected result when the value was not formatted correctly")] + [InlineData("ABCDEFGHIJKLMNOPQRSTUVWXYZ", "IFBEGRCFIZDUQSKKJNGE2TSPKBIVEU2UKVLFOWCZLI")] + [InlineData("abcdefghijklmnopqrstuvwxyz", "MFRGGZDFMZTWQ2LKNNWG23TPOBYXE43UOV3HO6DZPI")] + [InlineData("0123456789", "GAYTEMZUGU3DOOBZ")] + public void Base32TryFormatShouldProduceExpectedResultWhenValueWasNotFormattedCorrectly(string value, string expected) + { + // Given + ISpanFormattable candidate = new Base32(Encoding.UTF8.GetBytes(value)); + Span destination = stackalloc char[0]; + + // When + bool result = candidate.TryFormat(destination, out int charsWritten, [], null); + string actual = destination.ToString(); + + // Then + Assert.False(result); + Assert.NotEqual(expected, actual); + Assert.Equal(0, charsWritten); + } + + [Theory(DisplayName = "Base32.TryParse should produce the expected result when the string is parsed successfully")] + [InlineData("", "")] + [InlineData("ABCDEFGHIJKLMNOPQRSTUVWXYZ", "IFBEGRCFIZDUQSKKJNGE2TSPKBIVEU2UKVLFOWCZLI")] + [InlineData("abcdefghijklmnopqrstuvwxyz", "MFRGGZDFMZTWQ2LKNNWG23TPOBYXE43UOV3HO6DZPI")] + [InlineData("0123456789", "GAYTEMZUGU3DOOBZ")] + public void Base32TryParseShouldProduceTheExpectedResultWhenStringIsParsedSuccessfully(string expected, string value) + { + // When + bool result = Base32.TryParse(value, null, out Base32 candidate); + string actual = Encoding.UTF8.GetString(candidate.ToByteArray()); + + // Then + Assert.True(result); + Assert.Equal(expected, actual); + } + + [Theory(DisplayName = "Base32.TryParse should produce the expected result when the string is not parsed successfully")] + [InlineData("", "*INVALID_VALUE*")] + public void Base32TryParseShouldProduceTheExpectedResultWhenStringIsNotParsedSuccessfully(string expected, string value) + { + // When + bool result = Base32.TryParse(value, null, out Base32 candidate); + string actual = Encoding.UTF8.GetString(candidate.ToByteArray()); + + // Then + Assert.False(result); + Assert.Equal(expected, actual); + Assert.Equal(default, candidate); + } + + [Theory(DisplayName = "Base32.TryParse should produce the expected result when the ReadOnlySpan is parsed successfully")] + [InlineData("", "")] + [InlineData("ABCDEFGHIJKLMNOPQRSTUVWXYZ", "IFBEGRCFIZDUQSKKJNGE2TSPKBIVEU2UKVLFOWCZLI")] + [InlineData("abcdefghijklmnopqrstuvwxyz", "MFRGGZDFMZTWQ2LKNNWG23TPOBYXE43UOV3HO6DZPI")] + [InlineData("0123456789", "GAYTEMZUGU3DOOBZ")] + public void Base32TryParseShouldProduceTheExpectedResultWhenReadOnlySpanOfCharIsParsedSuccessfully(string expected, string value) + { + // When + bool result = Base32.TryParse(value.AsSpan(), null, out Base32 candidate); + string actual = Encoding.UTF8.GetString(candidate.ToByteArray()); + + // Then + Assert.True(result); + Assert.Equal(expected, actual); + } + + [Theory(DisplayName = "Base32.TryParse should produce the expected result when the ReadOnlySpan is not parsed successfully")] + [InlineData("", "*INVALID_VALUE*")] + public void Base32TryParseShouldProduceTheExpectedResultWhenReadOnlySpanOfCharIsNotParsedSuccessfully(string expected, string value) + { + // When + bool result = Base32.TryParse(value.AsSpan(), null, out Base32 candidate); + string actual = Encoding.UTF8.GetString(candidate.ToByteArray()); + + // Then + Assert.False(result); + Assert.Equal(expected, actual); + Assert.Equal(default, candidate); + } } diff --git a/OnixLabs.Core.UnitTests/Text/Base58CodecBitcoinTests.cs b/OnixLabs.Core.UnitTests/Text/Base58CodecBitcoinTests.cs deleted file mode 100644 index b0b0d1c..0000000 --- a/OnixLabs.Core.UnitTests/Text/Base58CodecBitcoinTests.cs +++ /dev/null @@ -1,58 +0,0 @@ -// Copyright 2020 ONIXLabs -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -using System.Text; -using OnixLabs.Core.Text; -using Xunit; - -namespace OnixLabs.Core.UnitTests.Text; - -public sealed class Base58CodecBitcoinTests -{ - [Theory(DisplayName = "Base58Codec.Encode should produce the expected result")] - [InlineData("", "")] - [InlineData("ABCDEFGHIJKLMNOPQRSTUVWXYZ","2zuFXTJSTRK6ESktqhM2QDBkCnH1U46CnxaD")] - [InlineData("abcdefghijklmnopqrstuvwxyz","3yxU3u1igY8WkgtjK92fbJQCd4BZiiT1v25f")] - [InlineData("0123456789","3i37NcgooY8f1S")] - public void Base58CodecEncodeShouldProduceExpectedResult(string value, string expected) - { - // Given - IBaseCodec codec = IBaseCodec.Base58; - byte[] bytes = value.ToByteArray(); - - // When - string actual = codec.Encode(bytes, Base58FormatProvider.Bitcoin); - - // Then - Assert.Equal(expected, actual); - } - - [Theory(DisplayName = "Base58Codec.Decode should produce the expected result")] - [InlineData("", "")] - [InlineData("2zuFXTJSTRK6ESktqhM2QDBkCnH1U46CnxaD","ABCDEFGHIJKLMNOPQRSTUVWXYZ")] - [InlineData("3yxU3u1igY8WkgtjK92fbJQCd4BZiiT1v25f","abcdefghijklmnopqrstuvwxyz")] - [InlineData("3i37NcgooY8f1S","0123456789")] - public void Base58CodecDecodeShouldProduceExpectedResult(string value, string expected) - { - // Given - IBaseCodec codec = IBaseCodec.Base58; - - // When - byte[] bytes = codec.Decode(value, Base58FormatProvider.Bitcoin); - string actual = Encoding.UTF8.GetString(bytes); - - // Then - Assert.Equal(expected, actual); - } -} diff --git a/OnixLabs.Core.UnitTests/Text/Base58CodecFlickrTests.cs b/OnixLabs.Core.UnitTests/Text/Base58CodecFlickrTests.cs deleted file mode 100644 index 9e148fd..0000000 --- a/OnixLabs.Core.UnitTests/Text/Base58CodecFlickrTests.cs +++ /dev/null @@ -1,58 +0,0 @@ -// Copyright 2020 ONIXLabs -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -using System.Text; -using OnixLabs.Core.Text; -using Xunit; - -namespace OnixLabs.Core.UnitTests.Text; - -public sealed class Base58CodecFlickrTests -{ - [Theory(DisplayName = "Base58Codec.Encode should produce the expected result")] - [InlineData("", "")] - [InlineData("ABCDEFGHIJKLMNOPQRSTUVWXYZ","2ZUfwsirsqj6erKTQGm2pdbKcMh1t46cMXzd")] - [InlineData("abcdefghijklmnopqrstuvwxyz","3YXt3U1HFx8vKFTJj92EAipcC4byHHs1V25E")] - [InlineData("0123456789","3H37nBFNNx8E1r")] - public void Base58CodecEncodeShouldProduceExpectedResult(string value, string expected) - { - // Given - IBaseCodec codec = IBaseCodec.Base58; - byte[] bytes = value.ToByteArray(); - - // When - string actual = codec.Encode(bytes, Base58FormatProvider.Flickr); - - // Then - Assert.Equal(expected, actual); - } - - [Theory(DisplayName = "Base58Codec.Decode should produce the expected result")] - [InlineData("", "")] - [InlineData("2ZUfwsirsqj6erKTQGm2pdbKcMh1t46cMXzd","ABCDEFGHIJKLMNOPQRSTUVWXYZ")] - [InlineData("3YXt3U1HFx8vKFTJj92EAipcC4byHHs1V25E","abcdefghijklmnopqrstuvwxyz")] - [InlineData("3H37nBFNNx8E1r","0123456789")] - public void Base58CodecDecodeShouldProduceExpectedResult(string value, string expected) - { - // Given - IBaseCodec codec = IBaseCodec.Base58; - - // When - byte[] bytes = codec.Decode(value, Base58FormatProvider.Flickr); - string actual = Encoding.UTF8.GetString(bytes); - - // Then - Assert.Equal(expected, actual); - } -} diff --git a/OnixLabs.Core.UnitTests/Text/Base58CodecRippleTests.cs b/OnixLabs.Core.UnitTests/Text/Base58CodecRippleTests.cs deleted file mode 100644 index 562835d..0000000 --- a/OnixLabs.Core.UnitTests/Text/Base58CodecRippleTests.cs +++ /dev/null @@ -1,58 +0,0 @@ -// Copyright 2020 ONIXLabs -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -using System.Text; -using OnixLabs.Core.Text; -using Xunit; - -namespace OnixLabs.Core.UnitTests.Text; - -public sealed class Base58CodecRippleTests -{ - [Theory(DisplayName = "Base58Codec.Encode should produce the expected result")] - [InlineData("", "")] - [InlineData("ABCDEFGHIJKLMNOPQRSTUVWXYZ", "pzuEXTJSTRKaNSktq6MpQDBkU8Hr7haU8x2D")] - [InlineData("abcdefghijklmnopqrstuvwxyz", "syx7sur5gY3WkgtjK9pCbJQUdhBZ55TrvpnC")] - [InlineData("0123456789", "s5sf4cgooY3CrS")] - public void Base58CodecEncodeShouldProduceExpectedResult(string value, string expected) - { - // Given - IBaseCodec codec = IBaseCodec.Base58; - byte[] bytes = value.ToByteArray(); - - // When - string actual = codec.Encode(bytes, Base58FormatProvider.Ripple); - - // Then - Assert.Equal(expected, actual); - } - - [Theory(DisplayName = "Base58Codec.Decode should produce the expected result")] - [InlineData("", "")] - [InlineData("pzuEXTJSTRKaNSktq6MpQDBkU8Hr7haU8x2D", "ABCDEFGHIJKLMNOPQRSTUVWXYZ")] - [InlineData("syx7sur5gY3WkgtjK9pCbJQUdhBZ55TrvpnC", "abcdefghijklmnopqrstuvwxyz")] - [InlineData("s5sf4cgooY3CrS", "0123456789")] - public void Base58CodecDecodeShouldProduceExpectedResult(string value, string expected) - { - // Given - IBaseCodec codec = IBaseCodec.Base58; - - // When - byte[] bytes = codec.Decode(value, Base58FormatProvider.Ripple); - string actual = Encoding.UTF8.GetString(bytes); - - // Then - Assert.Equal(expected, actual); - } -} diff --git a/OnixLabs.Core.UnitTests/Text/Base58CodecTests.cs b/OnixLabs.Core.UnitTests/Text/Base58CodecTests.cs new file mode 100644 index 0000000..e46b818 --- /dev/null +++ b/OnixLabs.Core.UnitTests/Text/Base58CodecTests.cs @@ -0,0 +1,204 @@ +// Copyright 2020 ONIXLabs +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +using System; +using System.Text; +using OnixLabs.Core.Text; +using OnixLabs.Core.UnitTests.Data; +using Xunit; + +namespace OnixLabs.Core.UnitTests.Text; + +public sealed class Base58CodecTests +{ + [Theory(DisplayName = "Base58Codec.Encode should produce the expected result (Bitcoin)")] + [InlineData("", "")] + [InlineData("ABCDEFGHIJKLMNOPQRSTUVWXYZ", "2zuFXTJSTRK6ESktqhM2QDBkCnH1U46CnxaD")] + [InlineData("abcdefghijklmnopqrstuvwxyz", "3yxU3u1igY8WkgtjK92fbJQCd4BZiiT1v25f")] + [InlineData("0123456789", "3i37NcgooY8f1S")] + public void Base58CodecEncodeShouldProduceExpectedResultBitcoin(string value, string expected) + { + // Given + IBaseCodec codec = IBaseCodec.Base58; + byte[] bytes = value.ToByteArray(); + + // When + string actual = codec.Encode(bytes); + + // Then + Assert.Equal(expected, actual); + } + + [Theory(DisplayName = "Base58Codec.Decode should produce the expected result (Bitcoin)")] + [InlineData("", "")] + [InlineData("2zuFXTJSTRK6ESktqhM2QDBkCnH1U46CnxaD", "ABCDEFGHIJKLMNOPQRSTUVWXYZ")] + [InlineData("3yxU3u1igY8WkgtjK92fbJQCd4BZiiT1v25f", "abcdefghijklmnopqrstuvwxyz")] + [InlineData("3i37NcgooY8f1S", "0123456789")] + public void Base58CodecDecodeShouldProduceExpectedResultBitcoin(string value, string expected) + { + // Given + IBaseCodec codec = IBaseCodec.Base58; + + // When + byte[] bytes = codec.Decode(value); + string actual = Encoding.UTF8.GetString(bytes); + + // Then + Assert.Equal(expected, actual); + } + + [Theory(DisplayName = "Base58Codec.Encode should produce the expected result (Flickr)")] + [InlineData("", "")] + [InlineData("ABCDEFGHIJKLMNOPQRSTUVWXYZ", "2ZUfwsirsqj6erKTQGm2pdbKcMh1t46cMXzd")] + [InlineData("abcdefghijklmnopqrstuvwxyz", "3YXt3U1HFx8vKFTJj92EAipcC4byHHs1V25E")] + [InlineData("0123456789", "3H37nBFNNx8E1r")] + public void Base58CodecEncodeShouldProduceExpectedResultFlickr(string value, string expected) + { + // Given + IBaseCodec codec = IBaseCodec.Base58; + byte[] bytes = value.ToByteArray(); + + // When + string actual = codec.Encode(bytes, Base58FormatProvider.Flickr); + + // Then + Assert.Equal(expected, actual); + } + + [Theory(DisplayName = "Base58Codec.Decode should produce the expected result (Flickr)")] + [InlineData("", "")] + [InlineData("2ZUfwsirsqj6erKTQGm2pdbKcMh1t46cMXzd", "ABCDEFGHIJKLMNOPQRSTUVWXYZ")] + [InlineData("3YXt3U1HFx8vKFTJj92EAipcC4byHHs1V25E", "abcdefghijklmnopqrstuvwxyz")] + [InlineData("3H37nBFNNx8E1r", "0123456789")] + public void Base58CodecDecodeShouldProduceExpectedResultFlickr(string value, string expected) + { + // Given + IBaseCodec codec = IBaseCodec.Base58; + + // When + byte[] bytes = codec.Decode(value, Base58FormatProvider.Flickr); + string actual = Encoding.UTF8.GetString(bytes); + + // Then + Assert.Equal(expected, actual); + } + + [Theory(DisplayName = "Base58Codec.Encode should produce the expected result (Ripple)")] + [InlineData("", "")] + [InlineData("ABCDEFGHIJKLMNOPQRSTUVWXYZ", "pzuEXTJSTRKaNSktq6MpQDBkU8Hr7haU8x2D")] + [InlineData("abcdefghijklmnopqrstuvwxyz", "syx7sur5gY3WkgtjK9pCbJQUdhBZ55TrvpnC")] + [InlineData("0123456789", "s5sf4cgooY3CrS")] + public void Base58CodecEncodeShouldProduceExpectedResultRipple(string value, string expected) + { + // Given + IBaseCodec codec = IBaseCodec.Base58; + byte[] bytes = value.ToByteArray(); + + // When + string actual = codec.Encode(bytes, Base58FormatProvider.Ripple); + + // Then + Assert.Equal(expected, actual); + } + + [Theory(DisplayName = "Base58Codec.Decode should produce the expected result (Ripple)")] + [InlineData("", "")] + [InlineData("pzuEXTJSTRKaNSktq6MpQDBkU8Hr7haU8x2D", "ABCDEFGHIJKLMNOPQRSTUVWXYZ")] + [InlineData("syx7sur5gY3WkgtjK9pCbJQUdhBZ55TrvpnC", "abcdefghijklmnopqrstuvwxyz")] + [InlineData("s5sf4cgooY3CrS", "0123456789")] + public void Base58CodecDecodeShouldProduceExpectedResultRipple(string value, string expected) + { + // Given + IBaseCodec codec = IBaseCodec.Base58; + + // When + byte[] bytes = codec.Decode(value, Base58FormatProvider.Ripple); + string actual = Encoding.UTF8.GetString(bytes); + + // Then + Assert.Equal(expected, actual); + } + + [Fact(DisplayName = "Base58Codec.Encode should throw FormatException when the format provider is invalid")] + public void Base58CodecEncodeShouldThrowFormatExceptionWhenFormatProviderIsInvalid() + { + // Given + IBaseCodec codec = IBaseCodec.Base58; + byte[] bytes = "Hello, World!".ToByteArray(); + + // When + Exception exception = Assert.Throws(() => codec.Encode(bytes, InvalidFormatProvider.Instance)); + + // Then + Assert.Equal("Encoding operation failed due to an invalid value or format provider.", exception.Message); + } + + [Fact(DisplayName = "Base58Codec.Decode should throw FormatException when the format provider is invalid")] + public void Base58CodecDecodeShouldThrowFormatExceptionWhenTheFormatProviderIsInvalid() + { + // Given + IBaseCodec codec = IBaseCodec.Base58; + const string value = "2zuFXTJSTRK6ESktqhM2QDBkCnH1U46CnxaD"; + + // When + Exception exception = Assert.Throws(() => codec.Decode(value, InvalidFormatProvider.Instance)); + + // Then + Assert.Equal("Decoding operation failed due to an invalid value or format provider.", exception.Message); + } + + [Fact(DisplayName = "Base58Codec.Decode should throw FormatException when the value is invalid")] + public void Base58CodecDecodeShouldThrowFormatExceptionWhenTheValueIsInvalid() + { + // Given + IBaseCodec codec = IBaseCodec.Base58; + const string value = "*INVALID_VALUE*"; + + // When + Exception exception = Assert.Throws(() => codec.Decode(value)); + + // Then + Assert.Equal("Decoding operation failed due to an invalid value or format provider.", exception.Message); + } + + [Fact(DisplayName = "Base58Codec.TryEncode should return false when the format provider is invalid")] + public void Base58CodecTryEncodeShouldReturnFalseWhenValueIsInvalid() + { + // Given + IBaseCodec codec = IBaseCodec.Base58; + byte[] bytes = "Hello, World!".ToByteArray(); + + // When + bool result = codec.TryEncode(bytes, InvalidFormatProvider.Instance, out string _); + + // Then + Assert.False(result); + } + + [Fact(DisplayName = "Base58Codec.TryEncode should pad zero values")] + public void Base58CodecTryEncodeShouldPadZeroValues() + { + // Given + IBaseCodec codec = IBaseCodec.Base58; + byte[] bytes = [0]; + const string expected = "1"; + + // When + bool result = codec.TryEncode(bytes, Base58FormatProvider.Bitcoin, out string actual); + + // Then + Assert.True(result); + Assert.Equal(expected, actual); + } +} diff --git a/OnixLabs.Core.UnitTests/Text/Base58FormatProviderTests.cs b/OnixLabs.Core.UnitTests/Text/Base58FormatProviderTests.cs new file mode 100644 index 0000000..d9ef5ca --- /dev/null +++ b/OnixLabs.Core.UnitTests/Text/Base58FormatProviderTests.cs @@ -0,0 +1,106 @@ +// Copyright 2020 ONIXLabs +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +using System; +using OnixLabs.Core.Text; +using Xunit; + +namespace OnixLabs.Core.UnitTests.Text; + +public sealed class Base58FormatProviderTests +{ + [Fact(DisplayName = "Base58FormatProvider.Bitcoin should produce the expected result")] + public void Base16FormatProviderBitcoinShouldProduceExpectedAlphabet() + { + // Given + const int expectedValue = 0; + const string expectedName = "Bitcoin"; + const string expectedAlphabet = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz"; + + // When + int actualValue = Base58FormatProvider.Bitcoin.Value; + string actualName = Base58FormatProvider.Bitcoin.Name; + string actualAlphabet = Base58FormatProvider.Bitcoin.Alphabet; + + // Then + Assert.Equal(expectedValue, actualValue); + Assert.Equal(expectedName, actualName); + Assert.Equal(expectedAlphabet, actualAlphabet); + } + + [Fact(DisplayName = "Base58FormatProvider.Flickr should produce the expected result")] + public void Base16FormatProviderFlickrShouldProduceExpectedAlphabet() + { + // Given + const int expectedValue = 1; + const string expectedName = "Flickr"; + const string expectedAlphabet = "123456789abcdefghijkmnopqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXYZ"; + + // When + int actualValue = Base58FormatProvider.Flickr.Value; + string actualName = Base58FormatProvider.Flickr.Name; + string actualAlphabet = Base58FormatProvider.Flickr.Alphabet; + + // Then + Assert.Equal(expectedValue, actualValue); + Assert.Equal(expectedName, actualName); + Assert.Equal(expectedAlphabet, actualAlphabet); + } + + [Fact(DisplayName = "Base58FormatProvider.Ripple should produce the expected result")] + public void Base16FormatProviderRippleShouldProduceExpectedAlphabet() + { + // Given + const int expectedValue = 2; + const string expectedName = "Ripple"; + const string expectedAlphabet = "rpshnaf39wBUDNEGHJKLM4PQRST7VWXYZ2bcdeCg65jkm8oFqi1tuvAxyz"; + + // When + int actualValue = Base58FormatProvider.Ripple.Value; + string actualName = Base58FormatProvider.Ripple.Name; + string actualAlphabet = Base58FormatProvider.Ripple.Alphabet; + + // Then + Assert.Equal(expectedValue, actualValue); + Assert.Equal(expectedName, actualName); + Assert.Equal(expectedAlphabet, actualAlphabet); + } + + [Fact(DisplayName = "Base58FormatProvider.GetFormat should return Base58FormatProvider when the given type is Base58FormatProvider")] + public void Base58FormatProviderShouldReturnBase58FormatProviderWhenGivenTypeIsBase58FormatProvider() + { + // Given + Type type = typeof(Base58FormatProvider); + + // When + object? result = Base58FormatProvider.Bitcoin.GetFormat(type); + + // Then + Assert.NotNull(result); + Assert.IsType(result); + } + + [Fact(DisplayName = "Base58FormatProvider.GetFormat should return null when the given type is not Base58FormatProvider")] + public void Base58FormatProviderShouldReturnNullWhenGivenTypeIsNotBase58FormatProvider() + { + // Given + Type type = typeof(object); + + // When + object? result = Base58FormatProvider.Bitcoin.GetFormat(type); + + // Then + Assert.Null(result); + } +} diff --git a/OnixLabs.Core.UnitTests/Text/Base58Tests.cs b/OnixLabs.Core.UnitTests/Text/Base58Tests.cs index 89a6055..c00a0cf 100644 --- a/OnixLabs.Core.UnitTests/Text/Base58Tests.cs +++ b/OnixLabs.Core.UnitTests/Text/Base58Tests.cs @@ -12,6 +12,8 @@ // See the License for the specific language governing permissions and // limitations under the License. +using System; +using System.Buffers; using System.Text; using OnixLabs.Core.Text; using Xunit; @@ -20,6 +22,81 @@ namespace OnixLabs.Core.UnitTests.Text; public sealed class Base58Tests { + [Fact(DisplayName = "Base58 should be implicitly constructable from byte[]")] + public void Base58ShouldBeImplicitlyConstructableFromByteArray() + { + // Given + const string expected = "qBLgTCSW82Hg"; + byte[] value = "ABCabc123".ToByteArray(); + + // When + Base58 candidate = value; + string actual = candidate.ToString(); + + // Then + Assert.Equal(expected, actual); + } + + [Fact(DisplayName = "Base58 should be implicitly constructable from ReadOnlySpan")] + public void Base58ShouldBeImplicitlyConstructableFromReadOnlySpanOfByte() + { + // Given + const string expected = "qBLgTCSW82Hg"; + ReadOnlySpan value = "ABCabc123".ToByteArray().AsSpan(); + + // When + Base58 candidate = value; + string actual = candidate.ToString(); + + // Then + Assert.Equal(expected, actual); + } + + [Fact(DisplayName = "Base58 should be implicitly constructable from string")] + public void Base58ShouldBeImplicitlyConstructableFromString() + { + // Given + const string expected = "qBLgTCSW82Hg"; + const string value = "ABCabc123"; + + // When + Base58 candidate = value; + string actual = candidate.ToString(); + + // Then + Assert.Equal(expected, actual); + } + + [Fact(DisplayName = "Base58 should be implicitly constructable from char[]")] + public void Base58ShouldBeImplicitlyConstructableFromCharArray() + { + // Given + const string expected = "qBLgTCSW82Hg"; + char[] value = "ABCabc123".ToCharArray(); + + // When + Base58 candidate = value; + string actual = candidate.ToString(); + + // Then + Assert.Equal(expected, actual); + } + + [Fact(DisplayName = "Base58 should be implicitly constructable from ReadOnlySequence")] + public void Base58ShouldBeImplicitlyConstructableFromReadOnlySequenceOfChar() + { + // Given + const string expected = "qBLgTCSW82Hg"; + ReadOnlySequence value = new("ABCabc123".ToCharArray()); + + // When + Base58 candidate = value; + string actual = candidate.ToString(); + + // Then + Assert.Equal(expected, actual); + } + [Fact(DisplayName = "Base58 should not change when modifying the original byte array")] public void Base58ShouldNotChangeWhenModifyingOriginalByteArray() { @@ -99,9 +176,9 @@ public void Base58ValuesShouldNotBeIdentical() [Theory(DisplayName = "Base58.Parse should produce the expected result")] [InlineData("", "")] - [InlineData("2zuFXTJSTRK6ESktqhM2QDBkCnH1U46CnxaD","ABCDEFGHIJKLMNOPQRSTUVWXYZ")] - [InlineData("3yxU3u1igY8WkgtjK92fbJQCd4BZiiT1v25f","abcdefghijklmnopqrstuvwxyz")] - [InlineData("3i37NcgooY8f1S","0123456789")] + [InlineData("2zuFXTJSTRK6ESktqhM2QDBkCnH1U46CnxaD", "ABCDEFGHIJKLMNOPQRSTUVWXYZ")] + [InlineData("3yxU3u1igY8WkgtjK92fbJQCd4BZiiT1v25f", "abcdefghijklmnopqrstuvwxyz")] + [InlineData("3i37NcgooY8f1S", "0123456789")] public void Base58ParseShouldProduceExpectedResult(string value, string expected) { // Given @@ -116,9 +193,9 @@ public void Base58ParseShouldProduceExpectedResult(string value, string expected [Theory(DisplayName = "Base58.ToString should produce the expected result")] [InlineData("", "")] - [InlineData("ABCDEFGHIJKLMNOPQRSTUVWXYZ","2zuFXTJSTRK6ESktqhM2QDBkCnH1U46CnxaD")] - [InlineData("abcdefghijklmnopqrstuvwxyz","3yxU3u1igY8WkgtjK92fbJQCd4BZiiT1v25f")] - [InlineData("0123456789","3i37NcgooY8f1S")] + [InlineData("ABCDEFGHIJKLMNOPQRSTUVWXYZ", "2zuFXTJSTRK6ESktqhM2QDBkCnH1U46CnxaD")] + [InlineData("abcdefghijklmnopqrstuvwxyz", "3yxU3u1igY8WkgtjK92fbJQCd4BZiiT1v25f")] + [InlineData("0123456789", "3i37NcgooY8f1S")] public void Base58ToStringShouldProduceExpectedResult(string value, string expected) { // Given @@ -131,4 +208,105 @@ public void Base58ToStringShouldProduceExpectedResult(string value, string expec // Then Assert.Equal(expected, actual); } + + [Theory(DisplayName = "Base58.TryFormat should produce the expected result when the value was formatted correctly")] + [InlineData("", "")] + [InlineData("ABCDEFGHIJKLMNOPQRSTUVWXYZ", "2zuFXTJSTRK6ESktqhM2QDBkCnH1U46CnxaD")] + [InlineData("abcdefghijklmnopqrstuvwxyz", "3yxU3u1igY8WkgtjK92fbJQCd4BZiiT1v25f")] + [InlineData("0123456789", "3i37NcgooY8f1S")] + public void Base58TryFormatShouldProduceExpectedResultWhenValueWasFormattedCorrectly(string value, string expected) + { + // Given + ISpanFormattable candidate = new Base58(Encoding.UTF8.GetBytes(value)); + Span destination = stackalloc char[expected.Length]; + + // When + bool result = candidate.TryFormat(destination, out int charsWritten, [], null); + string actual = destination.ToString(); + + // Then + Assert.True(result); + Assert.Equal(expected, actual); + Assert.Equal(expected.Length, charsWritten); + } + + [Theory(DisplayName = "Base58.TryFormat should produce the expected result when the value was not formatted correctly")] + [InlineData("ABCDEFGHIJKLMNOPQRSTUVWXYZ", "2zuFXTJSTRK6ESktqhM2QDBkCnH1U46CnxaD")] + [InlineData("abcdefghijklmnopqrstuvwxyz", "3yxU3u1igY8WkgtjK92fbJQCd4BZiiT1v25f")] + [InlineData("0123456789", "3i37NcgooY8f1S")] + public void Base58TryFormatShouldProduceExpectedResultWhenValueWasNotFormattedCorrectly(string value, string expected) + { + // Given + ISpanFormattable candidate = new Base58(Encoding.UTF8.GetBytes(value)); + Span destination = stackalloc char[0]; + + // When + bool result = candidate.TryFormat(destination, out int charsWritten, [], null); + string actual = destination.ToString(); + + // Then + Assert.False(result); + Assert.NotEqual(expected, actual); + Assert.Equal(0, charsWritten); + } + + [Theory(DisplayName = "Base58.TryParse should produce the expected result when the string is parsed successfully")] + [InlineData("", "")] + [InlineData("ABCDEFGHIJKLMNOPQRSTUVWXYZ", "2zuFXTJSTRK6ESktqhM2QDBkCnH1U46CnxaD")] + [InlineData("abcdefghijklmnopqrstuvwxyz", "3yxU3u1igY8WkgtjK92fbJQCd4BZiiT1v25f")] + [InlineData("0123456789", "3i37NcgooY8f1S")] + public void Base58TryParseShouldProduceTheExpectedResultWhenStringIsParsedSuccessfully(string expected, string value) + { + // When + bool result = Base58.TryParse(value, null, out Base58 candidate); + string actual = Encoding.UTF8.GetString(candidate.ToByteArray()); + + // Then + Assert.True(result); + Assert.Equal(expected, actual); + } + + [Theory(DisplayName = "Base58.TryParse should produce the expected result when the string is not parsed successfully")] + [InlineData("", "*INVALID_VALUE*")] + public void Base58TryParseShouldProduceTheExpectedResultWhenStringIsNotParsedSuccessfully(string expected, string value) + { + // When + bool result = Base58.TryParse(value, null, out Base58 candidate); + string actual = Encoding.UTF8.GetString(candidate.ToByteArray()); + + // Then + Assert.False(result); + Assert.Equal(expected, actual); + Assert.Equal(default, candidate); + } + + [Theory(DisplayName = "Base58.TryParse should produce the expected result when the ReadOnlySpan is parsed successfully")] + [InlineData("", "")] + [InlineData("ABCDEFGHIJKLMNOPQRSTUVWXYZ", "2zuFXTJSTRK6ESktqhM2QDBkCnH1U46CnxaD")] + [InlineData("abcdefghijklmnopqrstuvwxyz", "3yxU3u1igY8WkgtjK92fbJQCd4BZiiT1v25f")] + [InlineData("0123456789", "3i37NcgooY8f1S")] + public void Base58TryParseShouldProduceTheExpectedResultWhenReadOnlySpanOfCharIsParsedSuccessfully(string expected, string value) + { + // When + bool result = Base58.TryParse(value.AsSpan(), null, out Base58 candidate); + string actual = Encoding.UTF8.GetString(candidate.ToByteArray()); + + // Then + Assert.True(result); + Assert.Equal(expected, actual); + } + + [Theory(DisplayName = "Base58.TryParse should produce the expected result when the ReadOnlySpan is not parsed successfully")] + [InlineData("", "*INVALID_VALUE*")] + public void Base58TryParseShouldProduceTheExpectedResultWhenReadOnlySpanOfCharIsNotParsedSuccessfully(string expected, string value) + { + // When + bool result = Base58.TryParse(value.AsSpan(), null, out Base58 candidate); + string actual = Encoding.UTF8.GetString(candidate.ToByteArray()); + + // Then + Assert.False(result); + Assert.Equal(expected, actual); + Assert.Equal(default, candidate); + } } diff --git a/OnixLabs.Core.UnitTests/Text/Base64CodecTests.cs b/OnixLabs.Core.UnitTests/Text/Base64CodecTests.cs index fea69f2..beb53a6 100644 --- a/OnixLabs.Core.UnitTests/Text/Base64CodecTests.cs +++ b/OnixLabs.Core.UnitTests/Text/Base64CodecTests.cs @@ -12,14 +12,58 @@ // See the License for the specific language governing permissions and // limitations under the License. +using System; using System.Text; using OnixLabs.Core.Text; +using OnixLabs.Core.UnitTests.Data; using Xunit; namespace OnixLabs.Core.UnitTests.Text; public sealed class Base64CodecTests { + [Fact(DisplayName = "Base64Codec.Encode should throw FormatException when the format provider is invalid")] + public void Base64CodecEncodeShouldThrowFormatExceptionWhenFormatProviderIsInvalid() + { + // Given + IBaseCodec codec = IBaseCodec.Base64; + byte[] bytes = "Hello, World!".ToByteArray(); + + // When + Exception exception = Assert.Throws(() => codec.Encode(bytes, InvalidFormatProvider.Instance)); + + // Then + Assert.Equal("Encoding operation failed due to an invalid value or format provider.", exception.Message); + } + + [Fact(DisplayName = "Base64Codec.Decode should throw FormatException when the format provider is invalid")] + public void Base64CodecDecodeShouldThrowFormatExceptionWhenTheFormatProviderIsInvalid() + { + // Given + IBaseCodec codec = IBaseCodec.Base64; + const string value = "QUJDREVGR0hJSktMTU5PUFFSU1RVVldYWVo="; + + // When + Exception exception = Assert.Throws(() => codec.Decode(value, InvalidFormatProvider.Instance)); + + // Then + Assert.Equal("Decoding operation failed due to an invalid value or format provider.", exception.Message); + } + + [Fact(DisplayName = "Base64Codec.Decode should throw FormatException when the value is invalid")] + public void Base64CodecDecodeShouldThrowFormatExceptionWhenTheValueIsInvalid() + { + // Given + IBaseCodec codec = IBaseCodec.Base64; + const string value = "*INVALID_VALUE*"; + + // When + Exception exception = Assert.Throws(() => codec.Decode(value)); + + // Then + Assert.Equal("Decoding operation failed due to an invalid value or format provider.", exception.Message); + } + [Theory(DisplayName = "Base64Codec.Encode should produce the expected result")] [InlineData("", "")] [InlineData("ABCDEFGHIJKLMNOPQRSTUVWXYZ", "QUJDREVGR0hJSktMTU5PUFFSU1RVVldYWVo=")] diff --git a/OnixLabs.Core.UnitTests/Text/Base64FormatProviderTests.cs b/OnixLabs.Core.UnitTests/Text/Base64FormatProviderTests.cs new file mode 100644 index 0000000..89ac5c3 --- /dev/null +++ b/OnixLabs.Core.UnitTests/Text/Base64FormatProviderTests.cs @@ -0,0 +1,68 @@ +// Copyright 2020 ONIXLabs +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +using System; +using OnixLabs.Core.Text; +using Xunit; + +namespace OnixLabs.Core.UnitTests.Text; + +public sealed class Base64FormatProviderTests +{ + [Fact(DisplayName = "Base64FormatProvider.Rfc4648 should produce the expected result")] + public void Base64FormatProviderRfc4648ShouldProduceExpectedAlphabet() + { + // Given + const int expectedValue = 0; + const string expectedName = "Rfc4648"; + const string expectedAlphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/="; + + // When + int actualValue = Base64FormatProvider.Rfc4648.Value; + string actualName = Base64FormatProvider.Rfc4648.Name; + string actualAlphabet = Base64FormatProvider.Rfc4648.Alphabet; + + // Then + Assert.Equal(expectedValue, actualValue); + Assert.Equal(expectedName, actualName); + Assert.Equal(expectedAlphabet, actualAlphabet); + } + + [Fact(DisplayName = "Base64FormatProvider.GetFormat should return Base64FormatProvider when the given type is Base64FormatProvider")] + public void Base64FormatProviderShouldReturnBase64FormatProviderWhenGivenTypeIsBase64FormatProvider() + { + // Given + Type type = typeof(Base64FormatProvider); + + // When + object? result = Base64FormatProvider.Rfc4648.GetFormat(type); + + // Then + Assert.NotNull(result); + Assert.IsType(result); + } + + [Fact(DisplayName = "Base64FormatProvider.GetFormat should return null when the given type is not Base64FormatProvider")] + public void Base64FormatProviderShouldReturnNullWhenGivenTypeIsNotBase64FormatProvider() + { + // Given + Type type = typeof(object); + + // When + object? result = Base64FormatProvider.Rfc4648.GetFormat(type); + + // Then + Assert.Null(result); + } +} diff --git a/OnixLabs.Core.UnitTests/Text/Base64Tests.cs b/OnixLabs.Core.UnitTests/Text/Base64Tests.cs index 908ec3b..ade39a2 100644 --- a/OnixLabs.Core.UnitTests/Text/Base64Tests.cs +++ b/OnixLabs.Core.UnitTests/Text/Base64Tests.cs @@ -12,6 +12,8 @@ // See the License for the specific language governing permissions and // limitations under the License. +using System; +using System.Buffers; using System.Text; using OnixLabs.Core.Text; using Xunit; @@ -20,6 +22,81 @@ namespace OnixLabs.Core.UnitTests.Text; public sealed class Base64Tests { + [Fact(DisplayName = "Base64 should be implicitly constructable from byte[]")] + public void Base64ShouldBeImplicitlyConstructableFromByteArray() + { + // Given + const string expected = "QUJDYWJjMTIz"; + byte[] value = "ABCabc123".ToByteArray(); + + // When + Base64 candidate = value; + string actual = candidate.ToString(); + + // Then + Assert.Equal(expected, actual); + } + + [Fact(DisplayName = "Base64 should be implicitly constructable from ReadOnlySpan")] + public void Base64ShouldBeImplicitlyConstructableFromReadOnlySpanOfByte() + { + // Given + const string expected = "QUJDYWJjMTIz"; + ReadOnlySpan value = "ABCabc123".ToByteArray().AsSpan(); + + // When + Base64 candidate = value; + string actual = candidate.ToString(); + + // Then + Assert.Equal(expected, actual); + } + + [Fact(DisplayName = "Base64 should be implicitly constructable from string")] + public void Base64ShouldBeImplicitlyConstructableFromString() + { + // Given + const string expected = "QUJDYWJjMTIz"; + const string value = "ABCabc123"; + + // When + Base64 candidate = value; + string actual = candidate.ToString(); + + // Then + Assert.Equal(expected, actual); + } + + [Fact(DisplayName = "Base64 should be implicitly constructable from char[]")] + public void Base64ShouldBeImplicitlyConstructableFromCharArray() + { + // Given + const string expected = "QUJDYWJjMTIz"; + char[] value = "ABCabc123".ToCharArray(); + + // When + Base64 candidate = value; + string actual = candidate.ToString(); + + // Then + Assert.Equal(expected, actual); + } + + [Fact(DisplayName = "Base64 should be implicitly constructable from ReadOnlySequence")] + public void Base64ShouldBeImplicitlyConstructableFromReadOnlySequenceOfChar() + { + // Given + const string expected = "QUJDYWJjMTIz"; + ReadOnlySequence value = new("ABCabc123".ToCharArray()); + + // When + Base64 candidate = value; + string actual = candidate.ToString(); + + // Then + Assert.Equal(expected, actual); + } + [Fact(DisplayName = "Base64 should not change when modifying the original byte array")] public void Base64ShouldNotChangeWhenModifyingOriginalByteArray() { @@ -131,4 +208,105 @@ public void Base64ToStringShouldProduceExpectedResult(string value, string expec // Then Assert.Equal(expected, actual); } + + [Theory(DisplayName = "Base64.TryFormat should produce the expected result when the value was formatted correctly")] + [InlineData("", "")] + [InlineData("ABCDEFGHIJKLMNOPQRSTUVWXYZ", "QUJDREVGR0hJSktMTU5PUFFSU1RVVldYWVo=")] + [InlineData("abcdefghijklmnopqrstuvwxyz", "YWJjZGVmZ2hpamtsbW5vcHFyc3R1dnd4eXo=")] + [InlineData("0123456789", "MDEyMzQ1Njc4OQ==")] + public void Base64TryFormatShouldProduceExpectedResultWhenValueWasFormattedCorrectly(string value, string expected) + { + // Given + ISpanFormattable candidate = new Base64(Encoding.UTF8.GetBytes(value)); + Span destination = stackalloc char[expected.Length]; + + // When + bool result = candidate.TryFormat(destination, out int charsWritten, [], null); + string actual = destination.ToString(); + + // Then + Assert.True(result); + Assert.Equal(expected, actual); + Assert.Equal(expected.Length, charsWritten); + } + + [Theory(DisplayName = "Base64.TryFormat should produce the expected result when the value was not formatted correctly")] + [InlineData("ABCDEFGHIJKLMNOPQRSTUVWXYZ", "QUJDREVGR0hJSktMTU5PUFFSU1RVVldYWVo=")] + [InlineData("abcdefghijklmnopqrstuvwxyz", "YWJjZGVmZ2hpamtsbW5vcHFyc3R1dnd4eXo=")] + [InlineData("0123456789", "MDEyMzQ1Njc4OQ==")] + public void Base64TryFormatShouldProduceExpectedResultWhenValueWasNotFormattedCorrectly(string value, string expected) + { + // Given + ISpanFormattable candidate = new Base64(Encoding.UTF8.GetBytes(value)); + Span destination = stackalloc char[0]; + + // When + bool result = candidate.TryFormat(destination, out int charsWritten, [], null); + string actual = destination.ToString(); + + // Then + Assert.False(result); + Assert.NotEqual(expected, actual); + Assert.Equal(0, charsWritten); + } + + [Theory(DisplayName = "Base64.TryParse should produce the expected result when the string is parsed successfully")] + [InlineData("", "")] + [InlineData("ABCDEFGHIJKLMNOPQRSTUVWXYZ", "QUJDREVGR0hJSktMTU5PUFFSU1RVVldYWVo=")] + [InlineData("abcdefghijklmnopqrstuvwxyz", "YWJjZGVmZ2hpamtsbW5vcHFyc3R1dnd4eXo=")] + [InlineData("0123456789", "MDEyMzQ1Njc4OQ==")] + public void Base64TryParseShouldProduceTheExpectedResultWhenStringIsParsedSuccessfully(string expected, string value) + { + // When + bool result = Base64.TryParse(value, null, out Base64 candidate); + string actual = Encoding.UTF8.GetString(candidate.ToByteArray()); + + // Then + Assert.True(result); + Assert.Equal(expected, actual); + } + + [Theory(DisplayName = "Base64.TryParse should produce the expected result when the string is not parsed successfully")] + [InlineData("", "*INVALID VALUE*")] + public void Base64TryParseShouldProduceTheExpectedResultWhenStringIsNotParsedSuccessfully(string expected, string value) + { + // When + bool result = Base64.TryParse(value, null, out Base64 candidate); + string actual = Encoding.UTF8.GetString(candidate.ToByteArray()); + + // Then + Assert.False(result); + Assert.Equal(expected, actual); + Assert.Equal(default, candidate); + } + + [Theory(DisplayName = "Base64.TryParse should produce the expected result when the ReadOnlySpan is parsed successfully")] + [InlineData("", "")] + [InlineData("ABCDEFGHIJKLMNOPQRSTUVWXYZ", "QUJDREVGR0hJSktMTU5PUFFSU1RVVldYWVo=")] + [InlineData("abcdefghijklmnopqrstuvwxyz", "YWJjZGVmZ2hpamtsbW5vcHFyc3R1dnd4eXo=")] + [InlineData("0123456789", "MDEyMzQ1Njc4OQ==")] + public void Base64TryParseShouldProduceTheExpectedResultWhenReadOnlySpanOfCharIsParsedSuccessfully(string expected, string value) + { + // When + bool result = Base64.TryParse(value.AsSpan(), null, out Base64 candidate); + string actual = Encoding.UTF8.GetString(candidate.ToByteArray()); + + // Then + Assert.True(result); + Assert.Equal(expected, actual); + } + + [Theory(DisplayName = "Base64.TryParse should produce the expected result when the ReadOnlySpan is not parsed successfully")] + [InlineData("", "*INVALID VALUE*")] + public void Base64TryParseShouldProduceTheExpectedResultWhenReadOnlySpanOfCharIsNotParsedSuccessfully(string expected, string value) + { + // When + bool result = Base64.TryParse(value.AsSpan(), null, out Base64 candidate); + string actual = Encoding.UTF8.GetString(candidate.ToByteArray()); + + // Then + Assert.False(result); + Assert.Equal(expected, actual); + Assert.Equal(default, candidate); + } } diff --git a/OnixLabs.Core.UnitTests/Text/ExtensionTests.cs b/OnixLabs.Core.UnitTests/Text/ExtensionTests.cs new file mode 100644 index 0000000..2ab7e38 --- /dev/null +++ b/OnixLabs.Core.UnitTests/Text/ExtensionTests.cs @@ -0,0 +1,166 @@ +// Copyright 2020 ONIXLabs +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +using System; +using OnixLabs.Core.Text; +using Xunit; + +namespace OnixLabs.Core.UnitTests.Text; + +public sealed class ExtensionTests +{ + [Theory(DisplayName = "Byte[].ToBase16 should produce the expected result")] + [InlineData("", "")] + [InlineData("ABCDEFGHIJKLMNOPQRSTUVWXYZ", "4142434445464748494a4b4c4d4e4f505152535455565758595a")] + [InlineData("abcdefghijklmnopqrstuvwxyz", "6162636465666768696a6b6c6d6e6f707172737475767778797a")] + [InlineData("0123456789", "30313233343536373839")] + public void ByteArrayToBase16ShouldProduceExpectedResult(string value, string expected) + { + // Given + byte[] source = value.ToByteArray(); + + // When + Base16 result = source.ToBase16(); + string actual = result.ToString(); + + // Then + Assert.Equal(expected, actual); + } + + [Theory(DisplayName = "ReadOnlySpan.ToBase16 should produce the expected result")] + [InlineData("", "")] + [InlineData("ABCDEFGHIJKLMNOPQRSTUVWXYZ", "4142434445464748494a4b4c4d4e4f505152535455565758595a")] + [InlineData("abcdefghijklmnopqrstuvwxyz", "6162636465666768696a6b6c6d6e6f707172737475767778797a")] + [InlineData("0123456789", "30313233343536373839")] + public void ReadOnlySpanOfByteToBase16ShouldProduceExpectedResult(string value, string expected) + { + // Given + ReadOnlySpan source = value.ToByteArray().AsSpan(); + + // When + Base16 result = source.ToBase16(); + string actual = result.ToString(); + + // Then + Assert.Equal(expected, actual); + } + + [Theory(DisplayName = "Byte[].ToBase32 should produce the expected result")] + [InlineData("", "")] + [InlineData("ABCDEFGHIJKLMNOPQRSTUVWXYZ", "IFBEGRCFIZDUQSKKJNGE2TSPKBIVEU2UKVLFOWCZLI")] + [InlineData("abcdefghijklmnopqrstuvwxyz", "MFRGGZDFMZTWQ2LKNNWG23TPOBYXE43UOV3HO6DZPI")] + [InlineData("0123456789", "GAYTEMZUGU3DOOBZ")] + public void ByteArrayToBase32ShouldProduceExpectedResult(string value, string expected) + { + // Given + byte[] source = value.ToByteArray(); + + // When + Base32 result = source.ToBase32(); + string actual = result.ToString(); + + // Then + Assert.Equal(expected, actual); + } + + [Theory(DisplayName = "ReadOnlySpan.ToBase32 should produce the expected result")] + [InlineData("", "")] + [InlineData("ABCDEFGHIJKLMNOPQRSTUVWXYZ", "IFBEGRCFIZDUQSKKJNGE2TSPKBIVEU2UKVLFOWCZLI")] + [InlineData("abcdefghijklmnopqrstuvwxyz", "MFRGGZDFMZTWQ2LKNNWG23TPOBYXE43UOV3HO6DZPI")] + [InlineData("0123456789", "GAYTEMZUGU3DOOBZ")] + public void ReadOnlySpanOfByteToBase32ShouldProduceExpectedResult(string value, string expected) + { + // Given + ReadOnlySpan source = value.ToByteArray().AsSpan(); + + // When + Base32 result = source.ToBase32(); + string actual = result.ToString(); + + // Then + Assert.Equal(expected, actual); + } + + [Theory(DisplayName = "Byte[].ToBase58 should produce the expected result")] + [InlineData("", "")] + [InlineData("ABCDEFGHIJKLMNOPQRSTUVWXYZ", "2zuFXTJSTRK6ESktqhM2QDBkCnH1U46CnxaD")] + [InlineData("abcdefghijklmnopqrstuvwxyz", "3yxU3u1igY8WkgtjK92fbJQCd4BZiiT1v25f")] + [InlineData("0123456789", "3i37NcgooY8f1S")] + public void ByteArrayToBase58ShouldProduceExpectedResult(string value, string expected) + { + // Given + byte[] source = value.ToByteArray(); + + // When + Base58 result = source.ToBase58(); + string actual = result.ToString(); + + // Then + Assert.Equal(expected, actual); + } + + [Theory(DisplayName = "ReadOnlySpan.ToBase58 should produce the expected result")] + [InlineData("", "")] + [InlineData("ABCDEFGHIJKLMNOPQRSTUVWXYZ", "2zuFXTJSTRK6ESktqhM2QDBkCnH1U46CnxaD")] + [InlineData("abcdefghijklmnopqrstuvwxyz", "3yxU3u1igY8WkgtjK92fbJQCd4BZiiT1v25f")] + [InlineData("0123456789", "3i37NcgooY8f1S")] + public void ReadOnlySpanOfByteToBase58ShouldProduceExpectedResult(string value, string expected) + { + // Given + ReadOnlySpan source = value.ToByteArray().AsSpan(); + + // When + Base58 result = source.ToBase58(); + string actual = result.ToString(); + + // Then + Assert.Equal(expected, actual); + } + + [Theory(DisplayName = "Byte[].ToBase64 should produce the expected result")] + [InlineData("", "")] + [InlineData("ABCDEFGHIJKLMNOPQRSTUVWXYZ", "QUJDREVGR0hJSktMTU5PUFFSU1RVVldYWVo=")] + [InlineData("abcdefghijklmnopqrstuvwxyz", "YWJjZGVmZ2hpamtsbW5vcHFyc3R1dnd4eXo=")] + [InlineData("0123456789", "MDEyMzQ1Njc4OQ==")] + public void ByteArrayToBase64ShouldProduceExpectedResult(string value, string expected) + { + // Given + byte[] source = value.ToByteArray(); + + // When + Base64 result = source.ToBase64(); + string actual = result.ToString(); + + // Then + Assert.Equal(expected, actual); + } + + [Theory(DisplayName = "ReadOnlySpan.ToBase64 should produce the expected result")] + [InlineData("", "")] + [InlineData("ABCDEFGHIJKLMNOPQRSTUVWXYZ", "QUJDREVGR0hJSktMTU5PUFFSU1RVVldYWVo=")] + [InlineData("abcdefghijklmnopqrstuvwxyz", "YWJjZGVmZ2hpamtsbW5vcHFyc3R1dnd4eXo=")] + [InlineData("0123456789", "MDEyMzQ1Njc4OQ==")] + public void ReadOnlySpanOfByteToBase64ShouldProduceExpectedResult(string value, string expected) + { + // Given + ReadOnlySpan source = value.ToByteArray().AsSpan(); + + // When + Base64 result = source.ToBase64(); + string actual = result.ToString(); + + // Then + Assert.Equal(expected, actual); + } +} diff --git a/OnixLabs.Core.UnitTests/Text/IBaseCodecTests.cs b/OnixLabs.Core.UnitTests/Text/IBaseCodecTests.cs new file mode 100644 index 0000000..09638fa --- /dev/null +++ b/OnixLabs.Core.UnitTests/Text/IBaseCodecTests.cs @@ -0,0 +1,210 @@ +// Copyright 2020 ONIXLabs +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +using System; +using System.Text; +using OnixLabs.Core.Text; +using OnixLabs.Core.UnitTests.Data; +using Xunit; + +namespace OnixLabs.Core.UnitTests.Text; + +// ReSharper disable InconsistentNaming +public sealed class IBaseCodecTests +{ + [Fact(DisplayName = "IBaseCodec.GetString should return the expected result")] + public void IBaseCodecGetStringShouldReturnExpectedResult() + { + // Given + byte[] value = "Hello, World!".ToByteArray(); + const string expected = "48656c6c6f2c20576f726c6421"; + + // When + string actual = IBaseCodec.GetString(value, Base16FormatProvider.Invariant); + + // Then + Assert.Equal(expected, actual); + } + + [Fact(DisplayName = "IBaseCodec.GetString should throw FormatException when the format provider is invalid")] + public void IBaseCodecGetStringShouldThrowFormatExceptionWhenTheFormatProviderIsInvalid() + { + // Given + byte[] value = "Hello, World!".ToByteArray(); + + // When + Exception exception = Assert.Throws(() => IBaseCodec.GetString(value, InvalidFormatProvider.Instance)); + + // Then + Assert.Equal("Encoding operation failed due to an invalid value or format provider.", exception.Message); + } + + [Fact(DisplayName = "IBaseCodec.GetBytes should return the expected result")] + public void IBaseCodecGetBytesShouldReturnExpectedResult() + { + // Given + const string value = "48656c6c6f2c20576f726c6421"; + const string expected = "Hello, World!"; + + // When + byte[] bytes = IBaseCodec.GetBytes(value, Base16FormatProvider.Invariant); + string actual = Encoding.UTF8.GetString(bytes); + + // Then + Assert.Equal(expected, actual); + } + + [Fact(DisplayName = "IBaseCodec.GetBytes should throw FormatException when the format provider is invalid")] + public void IBaseCodecGetBytesShouldThrowFormatExceptionWhenFormatProviderIsInvalid() + { + // Given + const string value = "48656c6c6f2c20576f726c6421"; + + // When + Exception exception = Assert.Throws(() => IBaseCodec.GetBytes(value, InvalidFormatProvider.Instance)); + + // Then + Assert.Equal("Decoding operation failed due to an invalid value or format provider.", exception.Message); + } + + [Fact(DisplayName = "IBaseCodec.TryGetString should return true (Base16)")] + public void IBaseCodecTryGetStringShouldReturnTrueBase16() + { + // Given + byte[] value = "Hello, World!".ToByteArray(); + + // When + bool result = IBaseCodec.TryGetString(value, Base16FormatProvider.Invariant, out string _); + + // Then + Assert.True(result); + } + + [Fact(DisplayName = "IBaseCodec.TryGetString should return true (Base32)")] + public void IBaseCodecTryGetStringShouldReturnTrueBase32() + { + // Given + byte[] value = "Hello, World!".ToByteArray(); + + // When + bool result = IBaseCodec.TryGetString(value, Base32FormatProvider.Rfc4648, out string _); + + // Then + Assert.True(result); + } + + [Fact(DisplayName = "IBaseCodec.TryGetString should return true (Base58)")] + public void IBaseCodecTryGetStringShouldReturnTrueBase58() + { + // Given + byte[] value = "Hello, World!".ToByteArray(); + + // When + bool result = IBaseCodec.TryGetString(value, Base58FormatProvider.Bitcoin, out string _); + + // Then + Assert.True(result); + } + + [Fact(DisplayName = "IBaseCodec.TryGetString should return true (Base64)")] + public void IBaseCodecTryGetStringShouldReturnTrueBase64() + { + // Given + byte[] value = "Hello, World!".ToByteArray(); + + // When + bool result = IBaseCodec.TryGetString(value, Base64FormatProvider.Rfc4648, out string _); + + // Then + Assert.True(result); + } + + [Fact(DisplayName = "IBaseCodec.TryGetString should return false when the format provider is invalid")] + public void IBaseCodecTryGetStringShouldReturnFalseWhenFormatProviderIsInvalid() + { + // Given + byte[] value = "Hello, World!".ToByteArray(); + + // When + bool result = IBaseCodec.TryGetString(value, InvalidFormatProvider.Instance, out string _); + + // Then + Assert.False(result); + } + + [Fact(DisplayName = "IBaseCodec.TryGetBytes should return true (Base16)")] + public void IBaseCodecTryGetBytesShouldReturnTrueBase16() + { + // Given + const string value = "48656c6c6f2c20576f726c6421"; + + // When + bool result = IBaseCodec.TryGetBytes(value, Base16FormatProvider.Invariant, out byte[] _); + + // Then + Assert.True(result); + } + + [Fact(DisplayName = "IBaseCodec.TryGetBytes should return true (Base32)")] + public void IBaseCodecTryGetBytesShouldReturnTrueBase32() + { + // Given + const string value = "JBSWY3DPFQQFO33SNRSCC"; + + // When + bool result = IBaseCodec.TryGetBytes(value, Base32FormatProvider.Rfc4648, out byte[] _); + + // Then + Assert.True(result); + } + + [Fact(DisplayName = "IBaseCodec.TryGetBytes should return true (Base58)")] + public void IBaseCodecTryGetBytesShouldReturnTrueBase58() + { + // Given + const string value = "72k1xXWG59fYdzSNoA"; + + // When + bool result = IBaseCodec.TryGetBytes(value, Base58FormatProvider.Bitcoin, out byte[] _); + + // Then + Assert.True(result); + } + + [Fact(DisplayName = "IBaseCodec.TryGetBytes should return true (Base64)")] + public void IBaseCodecTryGetBytesShouldReturnTrueBase64() + { + // Given + const string value = "SGVsbG8sIFdvcmxkIQ=="; + + // When + bool result = IBaseCodec.TryGetBytes(value, Base64FormatProvider.Rfc4648, out byte[] _); + + // Then + Assert.True(result); + } + + [Fact(DisplayName = "IBaseCodec.TryGetBytes should return false when the format provider is invalid")] + public void IBaseCodecTryGetBytesShouldReturnFalseWhenFormatProviderIsInvalid() + { + // Given + const string value = "48656c6c6f2c20576f726c6421"; + + // When + bool result = IBaseCodec.TryGetBytes(value, InvalidFormatProvider.Instance, out byte[] _); + + // Then + Assert.False(result); + } +} diff --git a/OnixLabs.Core/Extensions.Object.cs b/OnixLabs.Core/Extensions.Object.cs index 47e7330..51661e3 100644 --- a/OnixLabs.Core/Extensions.Object.cs +++ b/OnixLabs.Core/Extensions.Object.cs @@ -71,7 +71,7 @@ public static bool IsWithinRangeExclusive(this T value, T min, T max) where T { null => 1, T other => comparable.CompareTo(other), - _ => throw new ArgumentException($"Object must be of type {typeof(T).Name}", nameof(obj)) + _ => throw new ArgumentException($"Object must be of type {typeof(T).FullName}", nameof(obj)) }; /// diff --git a/OnixLabs.Core/Extensions.Result.cs b/OnixLabs.Core/Extensions.Result.cs index be92f8e..44e2171 100644 --- a/OnixLabs.Core/Extensions.Result.cs +++ b/OnixLabs.Core/Extensions.Result.cs @@ -12,7 +12,9 @@ // See the License for the specific language governing permissions and // limitations under the License. +using System; using System.ComponentModel; +using System.Threading.Tasks; namespace OnixLabs.Core; @@ -22,6 +24,46 @@ namespace OnixLabs.Core; [EditorBrowsable(EditorBrowsableState.Never)] public static class ResultExtensions { + /// + /// Asynchronously gets the underlying value of the current instance, if the underlying value is present; + /// otherwise returns the default value. + /// + /// The current of . + /// The underlying type of the value. + /// + /// Returns the underlying value of the current instance, if the underlying value is present; + /// otherwise returns the default value. + /// + public static async Task GetValueOrDefaultAsync(this Task> task) => + (await task.ConfigureAwait(false)).GetValueOrDefault(); + + /// + /// Asynchronously gets the underlying value of the current instance, if the underlying value is present; + /// otherwise returns the specified default value. + /// + /// The current of . + /// The default value to return in the event that the underlying value is absent. + /// The underlying type of the value. + /// + /// Returns the underlying value of the current instance, if the underlying value is present; + /// otherwise returns the specified default value. + /// + public static async Task GetValueOrDefaultAsync(this Task> task, T defaultValue) => + (await task.ConfigureAwait(false)).GetValueOrDefault(defaultValue); + + /// + /// Asynchronously gets the underlying value of the current instance; + /// otherwise throws the underlying exception if the current is in a failed stated. + /// + /// The current of . + /// The underlying type of the value. + /// + /// Returns the underlying value of the current instance; + /// otherwise throws the underlying exception if the current is in a failed stated. + /// + public static async Task GetValueOrThrowAsync(this Task> task) => + (await task.ConfigureAwait(false)).GetValueOrThrow(); + /// /// Gets the value of the current instance, /// or if the result is in a state. @@ -35,6 +77,19 @@ public static class ResultExtensions public static Optional GetValueOrNone(this Result> result) where T : notnull => result is Success> { Value.HasValue: true } ? result.GetValueOrThrow() : Optional.None; + /// + /// Asynchronously gets the value of the current instance, + /// or if the result is in a state. + /// + /// The current of from which to obtain the value. + /// The underlying type of the value. + /// + /// Return the value of the current instance, + /// or if the result is in a state. + /// + public static async Task> GetValueOrNoneAsync(this Task>> task) where T : notnull => + (await task.ConfigureAwait(false)).GetValueOrNone(); + /// /// Gets the underlying value from the current of , /// or throws an exception if the result is in a state, or the is . @@ -48,6 +103,19 @@ public static Optional GetValueOrNone(this Result> result) whe public static T GetOptionalValueOrThrow(this Result> result) where T : notnull => result.GetValueOrThrow().GetValueOrThrow(); + /// + /// Asynchronously gets the underlying value from the current of , + /// or throws an exception if the result is in a state, or the is . + /// + /// The current of from which to obtain the value. + /// The underlying type of the value. + /// + /// Returns the underlying value from the current of , + /// or throws an exception if the result is in a state, or the is . + /// + public static async Task GetOptionalValueOrThrowAsync(this Task>> task) where T : notnull => + (await task.ConfigureAwait(false)).GetOptionalValueOrThrow(); + /// /// Gets the underlying value from the current of , /// or returns the default value if the result is in a state, or the is . @@ -61,4 +129,174 @@ public static T GetOptionalValueOrThrow(this Result> result) wher /// public static T GetOptionalValueOrDefault(this Result> result, T defaultValue) where T : notnull => result is Success> { Value.HasValue: true } ? result.GetOptionalValueOrThrow() : defaultValue; + + /// + /// Asynchronously gets the underlying value from the current of , + /// or returns the default value if the result is in a state, or the is . + /// + /// The current of from which to obtain the value. + /// The default value to return if the result is in a state, or the is . + /// The underlying type of the value. + /// + /// Returns the underlying value from the current of , + /// or returns the default value if the result is in a state, or the is . + /// + public static async Task GetOptionalValueOrDefaultAsync(this Task>> task, T defaultValue) where T : notnull => + (await task.ConfigureAwait(false)).GetOptionalValueOrDefault(defaultValue); + + /// + /// Asynchronously executes the action that matches the value of the current instance. + /// + /// The current of from which to execute the matching function. + /// The action to execute when the current instance is in a successful state. + /// The action to execute when the current instance is in a failed state. + public static async Task MatchAsync(this Task task, Action? success = null, Action? failure = null) => + (await task.ConfigureAwait(false)).Match(success, failure); + + /// + /// Asynchronously executes the function that matches the value of the current instance and returns its result. + /// + /// The current of from which to execute the matching function. + /// The action to execute when the current instance is in a successful state. + /// The action to execute when the current instance is in a failed state. + /// The underlying type of the result produced by the matching function. + /// + /// Returns the result of the function if the current instance is in a successful state; + /// otherwise, returns the result of the function if the current instance is in a failed state. + /// + public static async Task MatchAsync(this Task task, Func success, Func failure) => + (await task.ConfigureAwait(false)).Match(success, failure); + + /// + /// Asynchronously executes the action that matches the value of the current instance. + /// + /// The current of from which to execute the matching action. + /// The action to execute when the current instance is in a successful state. + /// The action to execute when the current instance is in a failed state. + /// The underlying type of the . + public static async Task MatchAsync(this Task> task, Action? success = null, Action? failure = null) => + (await task.ConfigureAwait(false)).Match(success, failure); + + /// + /// Asynchronously executes the function that matches the value of the current instance and returns its result. + /// + /// The current of from which to execute the matching function. + /// The action to execute when the current instance is in a successful state. + /// The action to execute when the current instance is in a failed state. + /// The underlying type of the . + /// The underlying type of the result produced by the matching function. + /// + /// Returns the result of the function if the current instance is in a successful state; + /// otherwise, returns the result of the function if the current instance is in a failed state. + /// + public static async Task MatchAsync(this Task> task, Func success, Func failure) => + (await task.ConfigureAwait(false)).Match(success, failure); + + /// + /// Asynchronously applies the provided selector action to the value of the current instance. + /// + /// The current of upon which to apply the selector action. + /// The action to apply to current instance. + /// + /// Returns if the current is in a successful state, and the action invocation is also successful; otherwise; . + /// + public static async Task SelectAsync(this Task task, Action selector) => + (await task.ConfigureAwait(false)).Select(selector); + + /// + /// Asynchronously applies the provided selector function to the value of the current instance. + /// + /// The current of upon which to apply the selector function. + /// The function to apply to the current instance. + /// The underlying type of the result produced by the selector function. + /// + /// Returns if the current is in a successful state, and the function invocation is also successful; otherwise; . + /// + public static async Task> SelectAsync(this Task task, Func selector) => + (await task.ConfigureAwait(false)).Select(selector); + + /// + /// Asynchronously applies the provided selector action to the value of the current instance. + /// + /// The current of upon which to apply the selector action. + /// The action to apply to the value of the current instance. + /// + /// Returns if the current is in a successful state, and the action invocation is also successful; otherwise; . + /// + public static async Task SelectAsync(this Task> task, Action selector) => + (await task.ConfigureAwait(false)).Select(selector); + + /// + /// Asynchronously applies the provided selector function to the value of the current instance. + /// + /// The current of upon which to apply the selector function. + /// The function to apply to the value of the current instance. + /// The underlying type of the . + /// The underlying type of the result produced by the selector function. + /// + /// Returns if the current is in a successful state, and the function invocation is also successful; otherwise; . + /// + public static async Task> SelectAsync(this Task> task, Func selector) => + (await task.ConfigureAwait(false)).Select(selector); + + /// + /// Asynchronously applies the provided selector function to the value of the current instance. + /// + /// The current of upon which to apply the selector function. + /// The function to apply to the current instance. + /// + /// Returns if the current is in a successful state, and the function invocation is also successful; otherwise; . + /// + public static async Task SelectManyAsync(this Task task, Func selector) => + (await task.ConfigureAwait(false)).SelectMany(selector); + + /// + /// Asynchronously applies the provided selector function to the value of the current instance. + /// + /// The current of upon which to apply the selector function. + /// The function to apply to the current instance. + /// The underlying type of the result produced by the selector function. + /// + /// Returns if the current is in a successful state, and the function invocation is also successful; otherwise; . + /// + public static async Task> SelectManyAsync(this Task task, Func> selector) => + (await task.ConfigureAwait(false)).SelectMany(selector); + + /// + /// Asynchronously applies the provided selector function to the value of the current instance. + /// + /// The current of upon which to apply the selector function. + /// The function to apply to the value of the current instance. + /// The underlying type of the . + /// + /// Returns if the current is in a successful state, and the function invocation is also successful; otherwise; . + /// + public static async Task SelectManyAsync(this Task> task, Func selector) => + (await task.ConfigureAwait(false)).SelectMany(selector); + + /// + /// Asynchronously applies the provided selector function to the value of the current instance. + /// + /// The current of upon which to apply the selector function. + /// The function to apply to the value of the current instance. + /// The underlying type of the . + /// The underlying type of the result produced by the selector function. + /// + /// Returns if the current is in a successful state, and the function invocation is also successful; otherwise; . + /// + public static async Task> SelectManyAsync(this Task> task, Func> selector) => + (await task.ConfigureAwait(false)).SelectMany(selector); + + /// + /// Asynchronously throws the underlying exception if the current is in a failure state. + /// + /// The current of from which to throw the underlying exception. + public static async Task ThrowAsync(this Task task) => (await task.ConfigureAwait(false)).Throw(); + + /// + /// Asynchronously throws the underlying exception if the current is in a failure state. + /// + /// The underlying type of the . + /// The current of from which to throw the underlying exception. + public static async Task ThrowAsync(this Task> task) => (await task.ConfigureAwait(false)).Throw(); } diff --git a/OnixLabs.Core/Extensions.String.cs b/OnixLabs.Core/Extensions.String.cs index ad88d4e..bc34f26 100644 --- a/OnixLabs.Core/Extensions.String.cs +++ b/OnixLabs.Core/Extensions.String.cs @@ -278,38 +278,6 @@ public static DateOnly ToDateOnly(this string value, IFormatProvider? provider = public static TimeOnly ToTimeOnly(this string value, IFormatProvider? provider = null, DateTimeStyles styles = DefaultStyles) => TimeOnly.Parse(value, provider, styles); - /// - /// Tries to copy the current instance into the destination . - /// - /// The current instance to copy. - /// The into which the current contents will be copied. - /// The number of characters written to the destination . - /// Returns if the current instance was copied into the destination ; otherwise, if the destination was too short. - public static bool TryCopyTo(this string value, Span destination, out int charsWritten) - { - bool result = value.TryCopyTo(destination); - charsWritten = result ? value.Length : 0; - return result; - } - - /// - /// Wraps the current instance between the specified before and after instances. - /// - /// The current instance to wrap. - /// The that should precede the current instance. - /// The that should succeed the current instance. - /// Returns a new instance representing the current instance, wrapped between the specified before and after instances. - public static string Wrap(this string value, char before, char after) => $"{before}{value}{after}"; - - /// - /// Wraps the current instance between the specified before and after instances. - /// - /// The current instance to wrap. - /// The that should precede the current instance. - /// The that should succeed the current instance. - /// Returns a new instance representing the current instance, wrapped between the specified before and after instances. - public static string Wrap(this string value, string before, string after) => $"{before}{value}{after}"; - /// /// Returns a with all escape characters formatted as escape character literals. /// @@ -350,4 +318,36 @@ public static string ToEscapedString(this string value) return result.ToString(); } + + /// + /// Tries to copy the current instance into the destination . + /// + /// The current instance to copy. + /// The into which the current contents will be copied. + /// The number of characters written to the destination . + /// Returns if the current instance was copied into the destination ; otherwise, if the destination was too short. + public static bool TryCopyTo(this string value, Span destination, out int charsWritten) + { + bool result = value.TryCopyTo(destination); + charsWritten = result ? value.Length : 0; + return result; + } + + /// + /// Wraps the current instance between the specified before and after instances. + /// + /// The current instance to wrap. + /// The that should precede the current instance. + /// The that should succeed the current instance. + /// Returns a new instance representing the current instance, wrapped between the specified before and after instances. + public static string Wrap(this string value, char before, char after) => $"{before}{value}{after}"; + + /// + /// Wraps the current instance between the specified before and after instances. + /// + /// The current instance to wrap. + /// The that should precede the current instance. + /// The that should succeed the current instance. + /// Returns a new instance representing the current instance, wrapped between the specified before and after instances. + public static string Wrap(this string value, string before, string after) => $"{before}{value}{after}"; } diff --git a/OnixLabs.Core/Linq/Extensions.IEnumerable.cs b/OnixLabs.Core/Linq/Extensions.IEnumerable.cs index b27169e..52845da 100644 --- a/OnixLabs.Core/Linq/Extensions.IEnumerable.cs +++ b/OnixLabs.Core/Linq/Extensions.IEnumerable.cs @@ -176,16 +176,15 @@ public static Optional LastOrNone(this IEnumerable enumerable, Func(this IEnumerable enumerable, Func predicate) => !enumerable.Any(predicate); /// Determines whether two sequences are , or equal by comparing their elements by using a specified . - /// An to compare to . - /// An to compare to the first sequence. + /// An to compare to . + /// An to compare to the first sequence. /// An to use to compare elements. /// The underlying type of the . /// Returns if the two source sequences are , or of equal length and their corresponding elements compare equal according to ; otherwise, . - public static bool SequenceEqualOrNull(this IEnumerable? first, IEnumerable? second, EqualityComparer? comparer = null) + public static bool SequenceEqualOrNull(this IEnumerable? enumerable, IEnumerable? other, EqualityComparer? comparer = null) { - if (first is null && second is null) return true; - if (first is null || second is null) return false; - return first.SequenceEqual(second, comparer ?? EqualityComparer.Default); + if (enumerable is null || other is null) return enumerable is null && other is null; + return enumerable.SequenceEqual(other, comparer ?? EqualityComparer.Default); } /// diff --git a/OnixLabs.Core/OnixLabs.Core.csproj b/OnixLabs.Core/OnixLabs.Core.csproj index 1faebc4..6aa7b5a 100644 --- a/OnixLabs.Core/OnixLabs.Core.csproj +++ b/OnixLabs.Core/OnixLabs.Core.csproj @@ -7,11 +7,11 @@ OnixLabs.Core ONIXLabs ONIXLabs Core API for .NET - 8.13.0 + 8.14.0 en Copyright © ONIXLabs 2020 https://github.com/onix-labs/onixlabs-dotnet - 8.13.0 + 8.14.0 $(AllowedOutputExtensionsInPackageBuildOutputFolder);.pdb diff --git a/OnixLabs.Core/Optional.cs b/OnixLabs.Core/Optional.cs index 6b08ba8..a7ae86b 100644 --- a/OnixLabs.Core/Optional.cs +++ b/OnixLabs.Core/Optional.cs @@ -26,7 +26,7 @@ public abstract class Optional : IValueEquatable> where T : notnu /// /// Gets a value indicating that the optional value is absent. /// - public static readonly Optional None = new None(); + public static readonly None None = new(); /// /// Initializes a new instance of the class. diff --git a/OnixLabs.Core/Result.Failure.cs b/OnixLabs.Core/Result.Failure.cs index f0a1d70..80c64bc 100644 --- a/OnixLabs.Core/Result.Failure.cs +++ b/OnixLabs.Core/Result.Failure.cs @@ -33,6 +33,15 @@ public sealed class Failure : Result, IValueEquatable /// public Exception Exception { get; } + /// + /// Creates a new instance of the class, where the underlying value represents a failed result. + /// + /// The underlying failed result exception. + /// + /// Returns a new instance of the class, where the underlying value represents a failed result. + /// + public static implicit operator Failure(Exception exception) => Failure(exception); + /// /// Performs an equality comparison between two object instances. /// @@ -71,6 +80,7 @@ public sealed class Failure : Result, IValueEquatable /// /// Executes the action that matches the value of the current instance. + /// In the case of a failure, the failure branch is invoked. /// /// The action to execute when the current instance is in a successful state. /// The action to execute when the current instance is in a failed state. @@ -78,9 +88,10 @@ public sealed class Failure : Result, IValueEquatable /// /// Executes the function that matches the value of the current instance and returns its result. + /// In the case of a failure, the failure branch is invoked. /// - /// The action to execute when the current instance is in a successful state. - /// The action to execute when the current instance is in a failed state. + /// The function to execute when the current instance is in a successful state. + /// The function to execute when the current instance is in a failed state. /// The underlying type of the result produced by the matching function. /// /// Returns the result of the function if the current instance is in a successful state; @@ -90,6 +101,7 @@ public sealed class Failure : Result, IValueEquatable /// /// Applies the provided selector action to the value of the current instance. + /// In the case of a failure, the current instance is returned. /// /// The action to apply to current instance. /// @@ -99,30 +111,33 @@ public sealed class Failure : Result, IValueEquatable /// /// Applies the provided selector function to the value of the current instance. + /// In the case of a failure, a new instance is returned with the current exception. /// /// The function to apply to the current instance. /// The underlying type of the result produced by the selector function. /// - /// Returns if the current is in a successful state, and the action invocation is also successful; otherwise; . + /// Returns if the current is in a successful state, and the function invocation is also successful; otherwise; . /// public override Result Select(Func selector) => Result.Failure(Exception); /// /// Applies the provided selector function to the value of the current instance. + /// In the case of a failure, the current instance is returned. /// /// The action to function to the current instance. /// - /// Returns if the current is in a successful state, and the action invocation is also successful; otherwise; . + /// Returns if the current is in a successful state, and the function invocation is also successful; otherwise; . /// public override Result SelectMany(Func selector) => this; /// /// Applies the provided selector function to the value of the current instance. + /// In the case of a failure, a new instance is returned with the current exception. /// /// The function to apply to the current instance. /// The underlying type of the result produced by the selector function. /// - /// Returns if the current is in a successful state, and the action invocation is also successful; otherwise; . + /// Returns if the current is in a successful state, and the function invocation is also successful; otherwise; . /// public override Result SelectMany(Func> selector) => Result.Failure(Exception); @@ -224,6 +239,7 @@ public sealed class Failure : Result, IValueEquatable> /// /// Executes the action that matches the value of the current instance. + /// In the case of a failure, the failure branch is invoked. /// /// The action to execute when the current instance is in a successful state. /// The action to execute when the current instance is in a failed state. @@ -231,9 +247,10 @@ public sealed class Failure : Result, IValueEquatable> /// /// Executes the function that matches the value of the current instance and returns its result. + /// In the case of a failure, the failure branch is invoked. /// - /// The action to execute when the current instance is in a successful state. - /// The action to execute when the current instance is in a failed state. + /// The function to execute when the current instance is in a successful state. + /// The function to execute when the current instance is in a failed state. /// The underlying type of the result produced by the matching function. /// /// Returns the result of the function if the current instance is in a successful state; @@ -243,6 +260,7 @@ public sealed class Failure : Result, IValueEquatable> /// /// Applies the provided selector action to the value of the current instance. + /// In the case of a failure, a new instance is returned with the current exception. /// /// The action to apply to the value of the current instance. /// @@ -252,30 +270,33 @@ public sealed class Failure : Result, IValueEquatable> /// /// Applies the provided selector function to the value of the current instance. + /// In the case of a failure, a new instance is returned with the current exception. /// /// The function to apply to the value of the current instance. /// The underlying type of the result produced by the selector function. /// - /// Returns if the current is in a successful state, and the action invocation is also successful; otherwise; . + /// Returns if the current is in a successful state, and the function invocation is also successful; otherwise; . /// public override Result Select(Func selector) => Result.Failure(Exception); /// /// Applies the provided selector function to the value of the current instance. + /// In the case of a failure, a new instance is returned with the current exception. /// /// The function to apply to the value of the current instance. /// - /// Returns if the current is in a successful state, and the action invocation is also successful; otherwise; . + /// Returns if the current is in a successful state, and the function invocation is also successful; otherwise; . /// public override Result SelectMany(Func selector) => Result.Failure(Exception); /// /// Applies the provided selector function to the value of the current instance. + /// In the case of a failure, a new instance is returned with the current exception. /// /// The function to apply to the value of the current instance. /// The underlying type of the result produced by the selector function. /// - /// Returns if the current is in a successful state, and the action invocation is also successful; otherwise; . + /// Returns if the current is in a successful state, and the function invocation is also successful; otherwise; . /// public override Result SelectMany(Func> selector) => Result.Failure(Exception); diff --git a/OnixLabs.Core/Result.Success.cs b/OnixLabs.Core/Result.Success.cs index d5db2ff..6fb7772 100644 --- a/OnixLabs.Core/Result.Success.cs +++ b/OnixLabs.Core/Result.Success.cs @@ -48,7 +48,7 @@ private Success() /// The left-hand instance to compare. /// The right-hand instance to compare. /// Returns if the left-hand instance is not equal to the right-hand instance; otherwise, . - public static bool operator !=(Success left, Success right) => Equals(left, right); + public static bool operator !=(Success left, Success right) => !Equals(left, right); /// /// Checks whether the current object is equal to another object of the same type. @@ -72,6 +72,7 @@ private Success() /// /// Executes the action that matches the value of the current instance. + /// In the case of a success, the success branch is invoked. /// /// The action to execute when the current instance is in a successful state. /// The action to execute when the current instance is in a failed state. @@ -79,9 +80,10 @@ private Success() /// /// Executes the function that matches the value of the current instance and returns its result. + /// In the case of a success, the success branch is invoked. /// - /// The action to execute when the current instance is in a successful state. - /// The action to execute when the current instance is in a failed state. + /// The function to execute when the current instance is in a successful state. + /// The function to execute when the current instance is in a failed state. /// The underlying type of the result produced by the matching function. /// /// Returns the result of the function if the current instance is in a successful state; @@ -91,6 +93,7 @@ private Success() /// /// Applies the provided selector action to the value of the current instance. + /// In the case of a success, the result of the selector is wrapped into a new instance. /// /// The action to apply to current instance. /// @@ -100,30 +103,33 @@ private Success() /// /// Applies the provided selector function to the value of the current instance. + /// In the case of a success, the result of the selector is wrapped into a new instance. /// /// The function to apply to the current instance. /// The underlying type of the result produced by the selector function. /// - /// Returns if the current is in a successful state, and the action invocation is also successful; otherwise; . + /// Returns if the current is in a successful state, and the function invocation is also successful; otherwise; . /// - public override Result Select(Func selector) => selector(); + public override Result Select(Func selector) => Result.Of(selector); /// /// Applies the provided selector function to the value of the current instance. + /// In the case of a success, the result of the selector is wrapped into a new instance. /// /// The function to apply to the current instance. /// - /// Returns if the current is in a successful state, and the action invocation is also successful; otherwise; . + /// Returns if the current is in a successful state, and the function invocation is also successful; otherwise; . /// public override Result SelectMany(Func selector) => selector(); /// /// Applies the provided selector function to the value of the current instance. + /// In the case of a success, the result of the selector is wrapped into a new instance. /// /// The function to apply to the current instance. /// The underlying type of the result produced by the selector function. /// - /// Returns if the current is in a successful state, and the action invocation is also successful; otherwise; . + /// Returns if the current is in a successful state, and the function invocation is also successful; otherwise; . /// public override Result SelectMany(Func> selector) => selector(); @@ -173,7 +179,7 @@ public sealed class Success : Result, IValueEquatable> /// The left-hand instance to compare. /// The right-hand instance to compare. /// Returns if the left-hand instance is not equal to the right-hand instance; otherwise, . - public static bool operator !=(Success left, Success right) => Equals(left, right); + public static bool operator !=(Success left, Success right) => !Equals(left, right); /// /// Checks whether the current object is equal to another object of the same type. @@ -228,6 +234,7 @@ public sealed class Success : Result, IValueEquatable> /// /// Executes the action that matches the value of the current instance. + /// In the case of a success, the success branch is invoked. /// /// The action to execute when the current instance is in a successful state. /// The action to execute when the current instance is in a failed state. @@ -235,9 +242,10 @@ public sealed class Success : Result, IValueEquatable> /// /// Executes the function that matches the value of the current instance and returns its result. + /// In the case of a success, the success branch is invoked. /// - /// The action to execute when the current instance is in a successful state. - /// The action to execute when the current instance is in a failed state. + /// The function to execute when the current instance is in a successful state. + /// The function to execute when the current instance is in a failed state. /// The underlying type of the result produced by the matching function. /// /// Returns the result of the function if the current instance is in a successful state; @@ -247,6 +255,7 @@ public sealed class Success : Result, IValueEquatable> /// /// Applies the provided selector action to the value of the current instance. + /// In the case of a success, the result of the selector is wrapped into a new instance. /// /// The action to apply to current instance. /// @@ -256,30 +265,33 @@ public sealed class Success : Result, IValueEquatable> /// /// Applies the provided selector function to the value of the current instance. + /// In the case of a success, the result of the selector is wrapped into a new instance. /// /// The function to apply to the current instance. /// The underlying type of the result produced by the selector function. /// - /// Returns if the current is in a successful state, and the action invocation is also successful; otherwise; . + /// Returns if the current is in a successful state, and the function invocation is also successful; otherwise; . /// - public override Result Select(Func selector) => selector(Value); + public override Result Select(Func selector) => Result.Of(() => selector(Value)); /// /// Applies the provided selector function to the value of the current instance. + /// In the case of a success, the result of the selector is wrapped into a new instance. /// /// The function to apply to the current instance. /// - /// Returns if the current is in a successful state, and the action invocation is also successful; otherwise; . + /// Returns if the current is in a successful state, and the function invocation is also successful; otherwise; . /// public override Result SelectMany(Func selector) => selector(Value); /// /// Applies the provided selector function to the value of the current instance. + /// In the case of a success, the result of the selector is wrapped into a new instance. /// /// The function to apply to the current instance. /// The underlying type of the result produced by the selector function. /// - /// Returns if the current is in a successful state, and the action invocation is also successful; otherwise; . + /// Returns if the current is in a successful state, and the function invocation is also successful; otherwise; . /// public override Result SelectMany(Func> selector) => selector(Value); diff --git a/OnixLabs.Core/Result.cs b/OnixLabs.Core/Result.cs index 890ec91..1c06f3c 100644 --- a/OnixLabs.Core/Result.cs +++ b/OnixLabs.Core/Result.cs @@ -210,7 +210,7 @@ public static async Task OfAsync(Func func, Can /// The function to apply to the current instance. /// The underlying type of the result produced by the selector function. /// - /// Returns if the current is in a successful state, and the action invocation is also successful; otherwise; . + /// Returns if the current is in a successful state, and the function invocation is also successful; otherwise; . /// public abstract Result Select(Func selector); @@ -219,7 +219,7 @@ public static async Task OfAsync(Func func, Can /// /// The function to apply to the current instance. /// - /// Returns if the current is in a successful state, and the action invocation is also successful; otherwise; . + /// Returns if the current is in a successful state, and the function invocation is also successful; otherwise; . /// public abstract Result SelectMany(Func selector); @@ -229,7 +229,7 @@ public static async Task OfAsync(Func func, Can /// The function to apply to the current instance. /// The underlying type of the result produced by the selector function. /// - /// Returns if the current is in a successful state, and the action invocation is also successful; otherwise; . + /// Returns if the current is in a successful state, and the function invocation is also successful; otherwise; . /// public abstract Result SelectMany(Func> selector); @@ -466,8 +466,8 @@ public static async Task> OfAsync(Func> fun /// /// Executes the function that matches the value of the current instance and returns its result. /// - /// The action to execute when the current instance is in a successful state. - /// The action to execute when the current instance is in a failed state. + /// The function to execute when the current instance is in a successful state. + /// The function to execute when the current instance is in a failed state. /// The underlying type of the result produced by the matching function. /// /// Returns the result of the function if the current instance is in a successful state; @@ -490,7 +490,7 @@ public static async Task> OfAsync(Func> fun /// The function to apply to the value of the current instance. /// The underlying type of the result produced by the selector function. /// - /// Returns if the current is in a successful state, and the action invocation is also successful; otherwise; . + /// Returns if the current is in a successful state, and the function invocation is also successful; otherwise; . /// public abstract Result Select(Func selector); @@ -499,7 +499,7 @@ public static async Task> OfAsync(Func> fun /// /// The function to apply to the value of the current instance. /// - /// Returns if the current is in a successful state, and the action invocation is also successful; otherwise; . + /// Returns if the current is in a successful state, and the function invocation is also successful; otherwise; . /// public abstract Result SelectMany(Func selector); @@ -509,7 +509,7 @@ public static async Task> OfAsync(Func> fun /// The function to apply to the value of the current instance. /// The underlying type of the result produced by the selector function. /// - /// Returns if the current is in a successful state, and the action invocation is also successful; otherwise; . + /// Returns if the current is in a successful state, and the function invocation is also successful; otherwise; . /// public abstract Result SelectMany(Func> selector); diff --git a/OnixLabs.Core/Text/Base16.Convertible.cs b/OnixLabs.Core/Text/Base16.Convertible.cs new file mode 100644 index 0000000..ecdfa2b --- /dev/null +++ b/OnixLabs.Core/Text/Base16.Convertible.cs @@ -0,0 +1,60 @@ +// Copyright 2020 ONIXLabs +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +using System; +using System.Buffers; +using System.Text; + +namespace OnixLabs.Core.Text; + +public readonly partial struct Base16 +{ + /// + /// Create a new instance from the specified value. + /// + /// The value from which to create a new instance. + /// Returns a new instance from the specified value. + public static implicit operator Base16(byte[] value) => new(value); + + /// + /// Create a new instance from the specified value. + /// + /// The value from which to create a new instance. + /// Returns a new instance from the specified value. + public static implicit operator Base16(ReadOnlySpan value) => new(value); + + /// + /// Create a new instance from the specified value. + /// The value will be encoded using the encoding. + /// + /// The value from which to create a new instance. + /// Returns a new instance from the specified value. + public static implicit operator Base16(string value) => new(Encoding.UTF8.GetBytes(value)); + + /// + /// Create a new instance from the specified value. + /// The value will be encoded using the encoding. + /// + /// The value from which to create a new instance. + /// Returns a new instance from the specified value. + public static implicit operator Base16(char[] value) => new(Encoding.UTF8.GetBytes(value)); + + /// + /// Create a new instance from the specified value. + /// The value will be encoded using the encoding. + /// + /// The value from which to create a new instance. + /// Returns a new instance from the specified value. + public static implicit operator Base16(ReadOnlySequence value) => new(Encoding.UTF8.GetBytes(value)); +} diff --git a/OnixLabs.Core/Text/Base16Codec.cs b/OnixLabs.Core/Text/Base16Codec.cs index fcc49b4..b32af14 100644 --- a/OnixLabs.Core/Text/Base16Codec.cs +++ b/OnixLabs.Core/Text/Base16Codec.cs @@ -70,14 +70,12 @@ public bool TryEncode(ReadOnlySpan value, IFormatProvider? provider, out s return true; } - if (provider is not null && provider is not Base16FormatProvider) + if (!IBaseCodec.TryGetFormatProvider(provider, Base16FormatProvider.Invariant, out Base16FormatProvider formatProvider)) { result = string.Empty; return false; } - Base16FormatProvider formatProvider = provider as Base16FormatProvider ?? Base16FormatProvider.Invariant; - result = formatProvider == Base16FormatProvider.Uppercase ? Convert.ToHexString(value) : Convert.ToHexString(value).ToLower(); @@ -111,14 +109,12 @@ public bool TryDecode(ReadOnlySpan value, IFormatProvider? provider, out b return true; } - if (provider is not null && provider is not Base16FormatProvider) + if (!IBaseCodec.TryGetFormatProvider(provider, Base16FormatProvider.Invariant, out Base16FormatProvider formatProvider)) { result = []; return false; } - Base16FormatProvider formatProvider = provider as Base16FormatProvider ?? Base16FormatProvider.Invariant; - if (formatProvider == Base16FormatProvider.Uppercase && value.ContainsAny(Base16LowercaseValues)) { result = []; diff --git a/OnixLabs.Core/Text/Base32.Convertible.cs b/OnixLabs.Core/Text/Base32.Convertible.cs new file mode 100644 index 0000000..0b90a97 --- /dev/null +++ b/OnixLabs.Core/Text/Base32.Convertible.cs @@ -0,0 +1,60 @@ +// Copyright 2020 ONIXLabs +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +using System; +using System.Buffers; +using System.Text; + +namespace OnixLabs.Core.Text; + +public readonly partial struct Base32 +{ + /// + /// Create a new instance from the specified value. + /// + /// The value from which to create a new instance. + /// Returns a new instance from the specified value. + public static implicit operator Base32(byte[] value) => new(value); + + /// + /// Create a new instance from the specified value. + /// + /// The value from which to create a new instance. + /// Returns a new instance from the specified value. + public static implicit operator Base32(ReadOnlySpan value) => new(value); + + /// + /// Create a new instance from the specified value. + /// The value will be encoded using the encoding. + /// + /// The value from which to create a new instance. + /// Returns a new instance from the specified value. + public static implicit operator Base32(string value) => new(Encoding.UTF8.GetBytes(value)); + + /// + /// Create a new instance from the specified value. + /// The value will be encoded using the encoding. + /// + /// The value from which to create a new instance. + /// Returns a new instance from the specified value. + public static implicit operator Base32(char[] value) => new(Encoding.UTF8.GetBytes(value)); + + /// + /// Create a new instance from the specified value. + /// The value will be encoded using the encoding. + /// + /// The value from which to create a new instance. + /// Returns a new instance from the specified value. + public static implicit operator Base32(ReadOnlySequence value) => new(Encoding.UTF8.GetBytes(value)); +} diff --git a/OnixLabs.Core/Text/Base32Codec.cs b/OnixLabs.Core/Text/Base32Codec.cs index 1db0cbd..d19b673 100644 --- a/OnixLabs.Core/Text/Base32Codec.cs +++ b/OnixLabs.Core/Text/Base32Codec.cs @@ -76,13 +76,12 @@ public bool TryEncode(ReadOnlySpan value, IFormatProvider? provider, out s return true; } - if (provider is not null && provider is not Base32FormatProvider) + if (!IBaseCodec.TryGetFormatProvider(provider, Base32FormatProvider.Rfc4648, out Base32FormatProvider formatProvider)) { result = string.Empty; return false; } - Base32FormatProvider formatProvider = provider as Base32FormatProvider ?? Base32FormatProvider.Rfc4648; StringBuilder builder = new(value.Length * InputSize / OutputSize); int inputPosition = 0; @@ -119,7 +118,7 @@ public bool TryEncode(ReadOnlySpan value, IFormatProvider? provider, out s return true; } - outputPosition <<= (OutputSize - outputSubPosition); + outputPosition <<= OutputSize - outputSubPosition; outputPosition &= 0x1F; builder.Append(formatProvider.Alphabet[outputPosition]); @@ -155,14 +154,12 @@ public bool TryDecode(ReadOnlySpan value, IFormatProvider? provider, out b return true; } - if (provider is not null && provider is not Base32FormatProvider) + if (!IBaseCodec.TryGetFormatProvider(provider, Base32FormatProvider.Rfc4648, out Base32FormatProvider formatProvider)) { result = []; return false; } - Base32FormatProvider formatProvider = provider as Base32FormatProvider ?? Base32FormatProvider.Rfc4648; - if (formatProvider.IsPadded && value.Length % InputSize != 0) { result = []; diff --git a/OnixLabs.Core/Text/Base58.Convertible.cs b/OnixLabs.Core/Text/Base58.Convertible.cs new file mode 100644 index 0000000..13afa64 --- /dev/null +++ b/OnixLabs.Core/Text/Base58.Convertible.cs @@ -0,0 +1,60 @@ +// Copyright 2020 ONIXLabs +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +using System; +using System.Buffers; +using System.Text; + +namespace OnixLabs.Core.Text; + +public readonly partial struct Base58 +{ + /// + /// Create a new instance from the specified value. + /// + /// The value from which to create a new instance. + /// Returns a new instance from the specified value. + public static implicit operator Base58(byte[] value) => new(value); + + /// + /// Create a new instance from the specified value. + /// + /// The value from which to create a new instance. + /// Returns a new instance from the specified value. + public static implicit operator Base58(ReadOnlySpan value) => new(value); + + /// + /// Create a new instance from the specified value. + /// The value will be encoded using the encoding. + /// + /// The value from which to create a new instance. + /// Returns a new instance from the specified value. + public static implicit operator Base58(string value) => new(Encoding.UTF8.GetBytes(value)); + + /// + /// Create a new instance from the specified value. + /// The value will be encoded using the encoding. + /// + /// The value from which to create a new instance. + /// Returns a new instance from the specified value. + public static implicit operator Base58(char[] value) => new(Encoding.UTF8.GetBytes(value)); + + /// + /// Create a new instance from the specified value. + /// The value will be encoded using the encoding. + /// + /// The value from which to create a new instance. + /// Returns a new instance from the specified value. + public static implicit operator Base58(ReadOnlySequence value) => new(Encoding.UTF8.GetBytes(value)); +} diff --git a/OnixLabs.Core/Text/Base58Codec.cs b/OnixLabs.Core/Text/Base58Codec.cs index 8b4c15e..16cde82 100644 --- a/OnixLabs.Core/Text/Base58Codec.cs +++ b/OnixLabs.Core/Text/Base58Codec.cs @@ -69,16 +69,17 @@ public bool TryEncode(ReadOnlySpan value, IFormatProvider? provider, out s return true; } - if (provider is not null && provider is not Base58FormatProvider) + if (!IBaseCodec.TryGetFormatProvider(provider, Base58FormatProvider.Bitcoin, out Base58FormatProvider formatProvider)) { result = string.Empty; return false; } - Base58FormatProvider formatProvider = provider as Base58FormatProvider ?? Base58FormatProvider.Bitcoin; StringBuilder builder = new(); BigInteger data = BigInteger.Zero; - foreach (byte b in value) data = data * 256 + b; + + foreach (byte b in value) + data = data * 256 + b; while (data > 0) { @@ -87,7 +88,8 @@ public bool TryEncode(ReadOnlySpan value, IFormatProvider? provider, out s builder.Insert(0, formatProvider.Alphabet[(int)remainder]); } - for (int index = 0; index < value.Length && value[index] == 0; index++) builder.Insert(0, '1'); + for (int index = 0; index < value.Length && value[index] == 0; index++) + builder.Insert(0, '1'); result = builder.ToString(); return true; @@ -119,14 +121,12 @@ public bool TryDecode(ReadOnlySpan value, IFormatProvider? provider, out b return true; } - if (provider is not null && provider is not Base58FormatProvider) + if (!IBaseCodec.TryGetFormatProvider(provider, Base58FormatProvider.Bitcoin, out Base58FormatProvider formatProvider)) { result = []; return false; } - Base58FormatProvider formatProvider = provider as Base58FormatProvider ?? Base58FormatProvider.Bitcoin; - BigInteger data = BigInteger.Zero; foreach (char character in value) diff --git a/OnixLabs.Core/Text/Base64.Convertible.cs b/OnixLabs.Core/Text/Base64.Convertible.cs new file mode 100644 index 0000000..1c8c114 --- /dev/null +++ b/OnixLabs.Core/Text/Base64.Convertible.cs @@ -0,0 +1,60 @@ +// Copyright 2020 ONIXLabs +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +using System; +using System.Buffers; +using System.Text; + +namespace OnixLabs.Core.Text; + +public readonly partial struct Base64 +{ + /// + /// Create a new instance from the specified value. + /// + /// The value from which to create a new instance. + /// Returns a new instance from the specified value. + public static implicit operator Base64(byte[] value) => new(value); + + /// + /// Create a new instance from the specified value. + /// + /// The value from which to create a new instance. + /// Returns a new instance from the specified value. + public static implicit operator Base64(ReadOnlySpan value) => new(value); + + /// + /// Create a new instance from the specified value. + /// The value will be encoded using the encoding. + /// + /// The value from which to create a new instance. + /// Returns a new instance from the specified value. + public static implicit operator Base64(string value) => new(Encoding.UTF8.GetBytes(value)); + + /// + /// Create a new instance from the specified value. + /// The value will be encoded using the encoding. + /// + /// The value from which to create a new instance. + /// Returns a new instance from the specified value. + public static implicit operator Base64(char[] value) => new(Encoding.UTF8.GetBytes(value)); + + /// + /// Create a new instance from the specified value. + /// The value will be encoded using the encoding. + /// + /// The value from which to create a new instance. + /// Returns a new instance from the specified value. + public static implicit operator Base64(ReadOnlySequence value) => new(Encoding.UTF8.GetBytes(value)); +} diff --git a/OnixLabs.Core/Text/Base64Codec.cs b/OnixLabs.Core/Text/Base64Codec.cs index 38df383..afd447f 100644 --- a/OnixLabs.Core/Text/Base64Codec.cs +++ b/OnixLabs.Core/Text/Base64Codec.cs @@ -59,6 +59,18 @@ public bool TryEncode(ReadOnlySpan value, IFormatProvider? provider, out s { try { + if (value.IsEmpty) + { + result = string.Empty; + return true; + } + + if (!IBaseCodec.TryGetFormatProvider(provider, Base64FormatProvider.Rfc4648, out Base64FormatProvider _)) + { + result = string.Empty; + return false; + } + result = Convert.ToBase64String(value); return true; } @@ -83,6 +95,18 @@ public bool TryDecode(ReadOnlySpan value, IFormatProvider? provider, out b { try { + if (value.IsEmpty) + { + result = []; + return true; + } + + if (!IBaseCodec.TryGetFormatProvider(provider, Base64FormatProvider.Rfc4648, out Base64FormatProvider _)) + { + result = []; + return false; + } + result = Convert.FromBase64String(value.ToString()); return true; } diff --git a/OnixLabs.Core/Text/Extensions.cs b/OnixLabs.Core/Text/Extensions.cs index 78ebdf1..d4807f9 100644 --- a/OnixLabs.Core/Text/Extensions.cs +++ b/OnixLabs.Core/Text/Extensions.cs @@ -18,7 +18,7 @@ namespace OnixLabs.Core.Text; /// -/// Provides extension methods for read-only spans. +/// Provides extension methods for byte arrays and read-only spans. /// // ReSharper disable UnusedMethodReturnValue.Global [EditorBrowsable(EditorBrowsableState.Never)] diff --git a/OnixLabs.Core/Text/IBaseCodec.cs b/OnixLabs.Core/Text/IBaseCodec.cs index 30a36bc..92604fb 100644 --- a/OnixLabs.Core/Text/IBaseCodec.cs +++ b/OnixLabs.Core/Text/IBaseCodec.cs @@ -13,6 +13,7 @@ // limitations under the License. using System; +using System.Globalization; namespace OnixLabs.Core.Text; @@ -24,12 +25,12 @@ public interface IBaseCodec /// /// The exception message to throw for encoding operations. /// - protected const string EncodingFormatException = "The specified value was not in the correct format and could not be encoded."; + protected const string EncodingFormatException = "Encoding operation failed due to an invalid value or format provider."; /// /// The exception message to throw for decoding operations. /// - protected const string DecodingFormatException = "The specified value was not in the correct format and could not be decoded."; + protected const string DecodingFormatException = "Decoding operation failed due to an invalid value or format provider."; /// /// Gets a new instance. @@ -131,6 +132,31 @@ public static bool TryGetBytes(ReadOnlySpan value, IFormatProvider provide }; } + /// + /// Tries to get the correct format provider, or the default provider is no format provider is present, or if the format provider is a culture info format provider. + /// + /// The format provider to check. + /// The default format provider to use if no format provider is present. + /// The correct format provider, or the default provider is no format provider is present. + /// The underlying type of the expected format provider. + /// Returns if the format provider is correct or not present; otherwise, if the format provider is not of the correct type. + protected static bool TryGetFormatProvider(IFormatProvider? provider, T defaultProvider, out T result) where T : IFormatProvider + { + switch (provider) + { + case null: + case CultureInfo: + result = defaultProvider; + return true; + case T typedFormatProvider: + result = typedFormatProvider; + return true; + } + + result = defaultProvider; + return false; + } + /// /// Encodes the specified value into a Base-N representation. /// diff --git a/OnixLabs.Numerics.UnitTests.Data/OnixLabs.Numerics.UnitTests.Data.csproj b/OnixLabs.Numerics.UnitTests.Data/OnixLabs.Numerics.UnitTests.Data.csproj index 60ca89f..db6aa9d 100644 --- a/OnixLabs.Numerics.UnitTests.Data/OnixLabs.Numerics.UnitTests.Data.csproj +++ b/OnixLabs.Numerics.UnitTests.Data/OnixLabs.Numerics.UnitTests.Data.csproj @@ -7,13 +7,13 @@ false - - - + + + runtime; build; native; contentfiles; analyzers; buildtransitive all - + runtime; build; native; contentfiles; analyzers; buildtransitive all diff --git a/OnixLabs.Numerics.UnitTests/OnixLabs.Numerics.UnitTests.csproj b/OnixLabs.Numerics.UnitTests/OnixLabs.Numerics.UnitTests.csproj index dfe9b76..2ac9c1a 100644 --- a/OnixLabs.Numerics.UnitTests/OnixLabs.Numerics.UnitTests.csproj +++ b/OnixLabs.Numerics.UnitTests/OnixLabs.Numerics.UnitTests.csproj @@ -8,13 +8,13 @@ true - - - + + + runtime; build; native; contentfiles; analyzers; buildtransitive all - + runtime; build; native; contentfiles; analyzers; buildtransitive all diff --git a/OnixLabs.Numerics/OnixLabs.Numerics.csproj b/OnixLabs.Numerics/OnixLabs.Numerics.csproj index 5a2926d..c40abc0 100644 --- a/OnixLabs.Numerics/OnixLabs.Numerics.csproj +++ b/OnixLabs.Numerics/OnixLabs.Numerics.csproj @@ -4,13 +4,13 @@ OnixLabs.Numerics ONIXLabs ONIXLabs Numerics API for .NET - 8.13.0 + 8.14.0 en enable true Copyright © ONIXLabs 2020 https://github.com/onix-labs/onixlabs-dotnet - 8.13.0 + 8.14.0 12 diff --git a/OnixLabs.Security.Cryptography.UnitTests.Data/OnixLabs.Security.Cryptography.UnitTests.Data.csproj b/OnixLabs.Security.Cryptography.UnitTests.Data/OnixLabs.Security.Cryptography.UnitTests.Data.csproj index ed3b5ce..73046a2 100644 --- a/OnixLabs.Security.Cryptography.UnitTests.Data/OnixLabs.Security.Cryptography.UnitTests.Data.csproj +++ b/OnixLabs.Security.Cryptography.UnitTests.Data/OnixLabs.Security.Cryptography.UnitTests.Data.csproj @@ -8,13 +8,13 @@ false - - - + + + runtime; build; native; contentfiles; analyzers; buildtransitive all - + runtime; build; native; contentfiles; analyzers; buildtransitive all diff --git a/OnixLabs.Security.Cryptography.UnitTests/HashAlgorithmExtensionTests.cs b/OnixLabs.Security.Cryptography.UnitTests/HashAlgorithmExtensionTests.cs index dd2dda3..ae0b4de 100644 --- a/OnixLabs.Security.Cryptography.UnitTests/HashAlgorithmExtensionTests.cs +++ b/OnixLabs.Security.Cryptography.UnitTests/HashAlgorithmExtensionTests.cs @@ -16,6 +16,7 @@ using System.IO; using System.Security.Cryptography; using System.Text; +using System.Threading.Tasks; using Xunit; namespace OnixLabs.Security.Cryptography.UnitTests; @@ -38,7 +39,7 @@ public void HashAlgorithmComputeHashShouldProduceExpectedResultWithTwoRounds() } [Fact(DisplayName = "HashAlgorithm.ComputeHashAsync should produce the expected result with two rounds")] - public async void HashAlgorithmComputeHashAsyncShouldProduceExpectedResultWithTwoRounds() + public async Task HashAlgorithmComputeHashAsyncShouldProduceExpectedResultWithTwoRounds() { // Given using HashAlgorithm algorithm = SHA256.Create(); diff --git a/OnixLabs.Security.Cryptography.UnitTests/HashTests.cs b/OnixLabs.Security.Cryptography.UnitTests/HashTests.cs index e358c77..3477422 100644 --- a/OnixLabs.Security.Cryptography.UnitTests/HashTests.cs +++ b/OnixLabs.Security.Cryptography.UnitTests/HashTests.cs @@ -15,6 +15,7 @@ using System; using System.IO; using System.Security.Cryptography; +using System.Threading.Tasks; using OnixLabs.Core; using Xunit; @@ -379,7 +380,7 @@ public void HashComputeShouldProduceTheExpectedHashUsingAStreamAndTwoRounds(stri [InlineData("abc123", "SHA256", "6ca13d52ca70c883e0f0bb101e425a89e8624de51db2d2392593af6a84118090")] [InlineData("abc123", "SHA384", "a31d79891919cad24f3264479d76884f581bee32e86778373db3a124de975dd86a40fc7f399b331133b281ab4b11a6ca")] [InlineData("abc123", "SHA512", "c70b5dd9ebfb6f51d09d4132b7170c9d20750a7852f00680f65658f0310e810056e6763c34c9a00b0e940076f54495c169fc2302cceb312039271c43469507dc")] - public async void HashComputeAsyncShouldProduceTheExpectedHashUsingAStream(string data, string algorithmName, string expected) + public async Task HashComputeAsyncShouldProduceTheExpectedHashUsingAStream(string data, string algorithmName, string expected) { // Given Stream stream = new MemoryStream(data.ToByteArray()); @@ -407,7 +408,7 @@ public async void HashComputeAsyncShouldProduceTheExpectedHashUsingAStream(strin [InlineData("abc123", "SHA256", "efaaeb3b1d1d85e8587ef0527ca43b9575ce8149ba1ee41583d3d19bd130daf8")] [InlineData("abc123", "SHA384", "d58e9a112b8c637df5d2e33af03ce738dd1c57657243d70d2fa8f76a99fa9a0e2f4abf50d9a88e8958f2d5f6fa002190")] [InlineData("abc123", "SHA512", "c2c9d705d7a1ed34247649bbe64c6edd2035e0a4c9ae1c063170f5ee2aeca09125cc0a8b30593c07a18801d6e0570de22e8dc40a59bc1f59a49834c05ed49949")] - public async void HashComputeAsyncShouldProduceTheExpectedHashUsingAStreamAndTwoRounds(string data, string algorithmName, string expected) + public async Task HashComputeAsyncShouldProduceTheExpectedHashUsingAStreamAndTwoRounds(string data, string algorithmName, string expected) { // Given Stream stream = new MemoryStream(data.ToByteArray()); diff --git a/OnixLabs.Security.Cryptography.UnitTests/MerkleTreeGenericTests.cs b/OnixLabs.Security.Cryptography.UnitTests/MerkleTreeGenericTests.cs index 4714f0e..8f9447b 100644 --- a/OnixLabs.Security.Cryptography.UnitTests/MerkleTreeGenericTests.cs +++ b/OnixLabs.Security.Cryptography.UnitTests/MerkleTreeGenericTests.cs @@ -53,6 +53,9 @@ public void IdenticalMerkleTreesShouldBeConsideredEqual() // Then Assert.Equal(a, b); Assert.Equal(a.Hash, b.Hash); + Assert.True(a.Equals(b)); + Assert.True(a == b); + Assert.False(a != b); } [Fact(DisplayName = "Different Merkle trees should not be considered equal")] @@ -65,6 +68,9 @@ public void DifferentMerkleTreesShouldNotBeConsideredEqual() // Then Assert.NotEqual(a, b); Assert.NotEqual(a.Hash, b.Hash); + Assert.False(a.Equals(b)); + Assert.False(a == b); + Assert.True(a != b); } [Fact(DisplayName = "MerkleTree.GetLeafHashes should produce the same leaf hashes that the tree was constructed with")] diff --git a/OnixLabs.Security.Cryptography.UnitTests/MerkleTreeTests.cs b/OnixLabs.Security.Cryptography.UnitTests/MerkleTreeTests.cs index 8a29552..4f7bc20 100644 --- a/OnixLabs.Security.Cryptography.UnitTests/MerkleTreeTests.cs +++ b/OnixLabs.Security.Cryptography.UnitTests/MerkleTreeTests.cs @@ -36,6 +36,9 @@ public void IdenticalMerkleTreesShouldBeConsideredEqual() // Then Assert.Equal(a, b); Assert.Equal(a.Hash, b.Hash); + Assert.True(a.Equals(b)); + Assert.True(a == b); + Assert.False(a != b); } [Fact(DisplayName = "Different Merkle trees should not be considered equal")] @@ -48,6 +51,9 @@ public void DifferentMerkleTreesShouldNotBeConsideredEqual() // Then Assert.NotEqual(a, b); Assert.NotEqual(a.Hash, b.Hash); + Assert.False(a.Equals(b)); + Assert.False(a == b); + Assert.True(a != b); } [Fact(DisplayName = "MerkleTree.GetLeafHashes should produce the same leaf hashes that the tree was constructed with")] diff --git a/OnixLabs.Security.Cryptography.UnitTests/OnixLabs.Security.Cryptography.UnitTests.csproj b/OnixLabs.Security.Cryptography.UnitTests/OnixLabs.Security.Cryptography.UnitTests.csproj index 39b888f..a7cb4fc 100644 --- a/OnixLabs.Security.Cryptography.UnitTests/OnixLabs.Security.Cryptography.UnitTests.csproj +++ b/OnixLabs.Security.Cryptography.UnitTests/OnixLabs.Security.Cryptography.UnitTests.csproj @@ -5,13 +5,13 @@ 12 - - - + + + runtime; build; native; contentfiles; analyzers; buildtransitive all - + runtime; build; native; contentfiles; analyzers; buildtransitive all diff --git a/OnixLabs.Security.Cryptography/DigitalSignature.Convertible.cs b/OnixLabs.Security.Cryptography/DigitalSignature.Convertible.cs new file mode 100644 index 0000000..67bcfe8 --- /dev/null +++ b/OnixLabs.Security.Cryptography/DigitalSignature.Convertible.cs @@ -0,0 +1,34 @@ +// Copyright 2020 ONIXLabs +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +using System; + +namespace OnixLabs.Security.Cryptography; + +public readonly partial struct DigitalSignature +{ + /// + /// Create a new instance from the specified value. + /// + /// The value from which to create a new instance. + /// Returns a new instance from the specified value. + public static implicit operator DigitalSignature(byte[] value) => new(value); + + /// + /// Create a new instance from the specified value. + /// + /// The value from which to create a new instance. + /// Returns a new instance from the specified value. + public static implicit operator DigitalSignature(ReadOnlySpan value) => new(value); +} diff --git a/OnixLabs.Security.Cryptography/EcdhPrivateKey.Convertible.cs b/OnixLabs.Security.Cryptography/EcdhPrivateKey.Convertible.cs new file mode 100644 index 0000000..a5f90e0 --- /dev/null +++ b/OnixLabs.Security.Cryptography/EcdhPrivateKey.Convertible.cs @@ -0,0 +1,34 @@ +// Copyright 2020 ONIXLabs +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +using System; + +namespace OnixLabs.Security.Cryptography; + +public sealed partial class EcdhPrivateKey +{ + /// + /// Create a new instance from the specified value. + /// + /// The value from which to create a new instance. + /// Returns a new instance from the specified value. + public static implicit operator EcdhPrivateKey(byte[] value) => new(value); + + /// + /// Create a new instance from the specified value. + /// + /// The value from which to create a new instance. + /// Returns a new instance from the specified value. + public static implicit operator EcdhPrivateKey(ReadOnlySpan value) => new(value); +} diff --git a/OnixLabs.Security.Cryptography/EcdhPrivateKey.Import.cs b/OnixLabs.Security.Cryptography/EcdhPrivateKey.Import.cs index bac0421..fd98614 100644 --- a/OnixLabs.Security.Cryptography/EcdhPrivateKey.Import.cs +++ b/OnixLabs.Security.Cryptography/EcdhPrivateKey.Import.cs @@ -14,6 +14,7 @@ using System; using System.Security.Cryptography; +using OnixLabs.Core; namespace OnixLabs.Security.Cryptography; @@ -24,7 +25,8 @@ public sealed partial class EcdhPrivateKey /// /// The EC Diffie-Hellman cryptographic private key data to import. /// Returns a new EC Diffie-Hellman cryptographic private key from the imported data. - public static EcdhPrivateKey ImportPkcs8PrivateKey(ReadOnlySpan data) => ImportPkcs8PrivateKey(data, out int _); + public static EcdhPrivateKey ImportPkcs8PrivateKey(ReadOnlySpan data) => + ImportPkcs8PrivateKey(data, out int _); /// /// Imports the EC Diffie-Hellman cryptographic private key data in PKCS #8 format. @@ -40,13 +42,31 @@ public static EcdhPrivateKey ImportPkcs8PrivateKey(ReadOnlySpan data, out return new EcdhPrivateKey(keyData); } + /// + /// Imports the EC Diffie-Hellman cryptographic private key data in PKCS #8 format. + /// + /// The EC Diffie-Hellman cryptographic private key data to import. + /// Returns a new EC Diffie-Hellman cryptographic private key from the imported data. + public static EcdhPrivateKey ImportPkcs8PrivateKey(IBinaryConvertible data) => + ImportPkcs8PrivateKey(data, out int _); + + /// + /// Imports the EC Diffie-Hellman cryptographic private key data in PKCS #8 format. + /// + /// The EC Diffie-Hellman cryptographic private key data to import. + /// The number of bytes read from the input data. + /// Returns a new EC Diffie-Hellman cryptographic private key from the imported data. + public static EcdhPrivateKey ImportPkcs8PrivateKey(IBinaryConvertible data, out int bytesRead) => + ImportPkcs8PrivateKey(data.ToByteArray(), out bytesRead); + /// /// Imports the EC Diffie-Hellman cryptographic private key data in encrypted PKCS #8 format. /// /// The EC Diffie-Hellman cryptographic private key data to import. /// The password required for password based decryption. /// Returns a new EC Diffie-Hellman cryptographic private key from the imported data. - public static EcdhPrivateKey ImportPkcs8PrivateKey(ReadOnlySpan data, ReadOnlySpan password) => ImportPkcs8PrivateKey(data, password, out int _); + public static EcdhPrivateKey ImportPkcs8PrivateKey(ReadOnlySpan data, ReadOnlySpan password) => + ImportPkcs8PrivateKey(data, password, out int _); /// /// Imports the EC Diffie-Hellman cryptographic private key data in encrypted PKCS #8 format. @@ -63,6 +83,25 @@ public static EcdhPrivateKey ImportPkcs8PrivateKey(ReadOnlySpan data, Read return new EcdhPrivateKey(keyData); } + /// + /// Imports the EC Diffie-Hellman cryptographic private key data in encrypted PKCS #8 format. + /// + /// The EC Diffie-Hellman cryptographic private key data to import. + /// The password required for password based decryption. + /// Returns a new EC Diffie-Hellman cryptographic private key from the imported data. + public static EcdhPrivateKey ImportPkcs8PrivateKey(IBinaryConvertible data, ReadOnlySpan password) => + ImportPkcs8PrivateKey(data, password, out int _); + + /// + /// Imports the EC Diffie-Hellman cryptographic private key data in encrypted PKCS #8 format. + /// + /// The EC Diffie-Hellman cryptographic private key data to import. + /// The password required for password based decryption. + /// The number of bytes read from the input data. + /// Returns a new EC Diffie-Hellman cryptographic private key from the imported data. + public static EcdhPrivateKey ImportPkcs8PrivateKey(IBinaryConvertible data, ReadOnlySpan password, out int bytesRead) => + ImportPkcs8PrivateKey(data.ToByteArray(), password, out bytesRead); + /// /// Imports the key data into a new instance. /// diff --git a/OnixLabs.Security.Cryptography/EcdhPublicKey.Convertible.cs b/OnixLabs.Security.Cryptography/EcdhPublicKey.Convertible.cs new file mode 100644 index 0000000..df6dbf8 --- /dev/null +++ b/OnixLabs.Security.Cryptography/EcdhPublicKey.Convertible.cs @@ -0,0 +1,34 @@ +// Copyright 2020 ONIXLabs +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +using System; + +namespace OnixLabs.Security.Cryptography; + +public sealed partial class EcdhPublicKey +{ + /// + /// Create a new instance from the specified value. + /// + /// The value from which to create a new instance. + /// Returns a new instance from the specified value. + public static implicit operator EcdhPublicKey(byte[] value) => new(value); + + /// + /// Create a new instance from the specified value. + /// + /// The value from which to create a new instance. + /// Returns a new instance from the specified value. + public static implicit operator EcdhPublicKey(ReadOnlySpan value) => new(value); +} diff --git a/OnixLabs.Security.Cryptography/EcdsaPrivateKey.Convertible.cs b/OnixLabs.Security.Cryptography/EcdsaPrivateKey.Convertible.cs new file mode 100644 index 0000000..65b3765 --- /dev/null +++ b/OnixLabs.Security.Cryptography/EcdsaPrivateKey.Convertible.cs @@ -0,0 +1,34 @@ +// Copyright 2020 ONIXLabs +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +using System; + +namespace OnixLabs.Security.Cryptography; + +public sealed partial class EcdsaPrivateKey +{ + /// + /// Create a new instance from the specified value. + /// + /// The value from which to create a new instance. + /// Returns a new instance from the specified value. + public static implicit operator EcdsaPrivateKey(byte[] value) => new(value); + + /// + /// Create a new instance from the specified value. + /// + /// The value from which to create a new instance. + /// Returns a new instance from the specified value. + public static implicit operator EcdsaPrivateKey(ReadOnlySpan value) => new(value); +} diff --git a/OnixLabs.Security.Cryptography/EcdsaPrivateKey.Import.cs b/OnixLabs.Security.Cryptography/EcdsaPrivateKey.Import.cs index 2bf1bb4..5cc0ea6 100644 --- a/OnixLabs.Security.Cryptography/EcdsaPrivateKey.Import.cs +++ b/OnixLabs.Security.Cryptography/EcdsaPrivateKey.Import.cs @@ -14,6 +14,7 @@ using System; using System.Security.Cryptography; +using OnixLabs.Core; namespace OnixLabs.Security.Cryptography; @@ -24,7 +25,8 @@ public sealed partial class EcdsaPrivateKey /// /// The cryptographic private key data to import. /// Returns a new instance from the imported cryptographic private key data. - public static EcdsaPrivateKey ImportPkcs8PrivateKey(ReadOnlySpan data) => ImportPkcs8PrivateKey(data, out int _); + public static EcdsaPrivateKey ImportPkcs8PrivateKey(ReadOnlySpan data) => + ImportPkcs8PrivateKey(data, out int _); /// /// Imports the ECDSA cryptographic private key data in PKCS #8 format. @@ -40,13 +42,31 @@ public static EcdsaPrivateKey ImportPkcs8PrivateKey(ReadOnlySpan data, out return new EcdsaPrivateKey(keyData); } + /// + /// Imports the ECDSA cryptographic private key data in PKCS #8 format. + /// + /// The cryptographic private key data to import. + /// Returns a new instance from the imported cryptographic private key data. + public static EcdsaPrivateKey ImportPkcs8PrivateKey(IBinaryConvertible data) => + ImportPkcs8PrivateKey(data, out int _); + + /// + /// Imports the ECDSA cryptographic private key data in PKCS #8 format. + /// + /// The cryptographic private key data to import. + /// The number of bytes read from the input data. + /// Returns a new instance from the imported cryptographic private key data. + public static EcdsaPrivateKey ImportPkcs8PrivateKey(IBinaryConvertible data, out int bytesRead) => + ImportPkcs8PrivateKey(data.ToByteArray(), out bytesRead); + /// /// Imports the ECDSA cryptographic private key data in encrypted PKCS #8 format. /// /// The cryptographic private key data to import. /// The password required for password based decryption. /// Returns a new instance from the imported cryptographic private key data. - public static EcdsaPrivateKey ImportPkcs8PrivateKey(ReadOnlySpan data, ReadOnlySpan password) => ImportPkcs8PrivateKey(data, password, out int _); + public static EcdsaPrivateKey ImportPkcs8PrivateKey(ReadOnlySpan data, ReadOnlySpan password) => + ImportPkcs8PrivateKey(data, password, out int _); /// /// Imports the ECDSA cryptographic private key data in encrypted PKCS #8 format. @@ -63,6 +83,25 @@ public static EcdsaPrivateKey ImportPkcs8PrivateKey(ReadOnlySpan data, Rea return new EcdsaPrivateKey(keyData); } + /// + /// Imports the ECDSA cryptographic private key data in encrypted PKCS #8 format. + /// + /// The cryptographic private key data to import. + /// The password required for password based decryption. + /// Returns a new instance from the imported cryptographic private key data. + public static EcdsaPrivateKey ImportPkcs8PrivateKey(IBinaryConvertible data, ReadOnlySpan password) => + ImportPkcs8PrivateKey(data, password, out int _); + + /// + /// Imports the ECDSA cryptographic private key data in encrypted PKCS #8 format. + /// + /// The cryptographic private key data to import. + /// The password required for password based decryption. + /// The number of bytes read from the input data. + /// Returns a new instance from the imported cryptographic private key data. + public static EcdsaPrivateKey ImportPkcs8PrivateKey(IBinaryConvertible data, ReadOnlySpan password, out int bytesRead) => + ImportPkcs8PrivateKey(data.ToByteArray(), password, out bytesRead); + /// /// Imports the key data into a new instance. /// diff --git a/OnixLabs.Security.Cryptography/EcdsaPublicKey.Convertible.cs b/OnixLabs.Security.Cryptography/EcdsaPublicKey.Convertible.cs new file mode 100644 index 0000000..164f367 --- /dev/null +++ b/OnixLabs.Security.Cryptography/EcdsaPublicKey.Convertible.cs @@ -0,0 +1,34 @@ +// Copyright 2020 ONIXLabs +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +using System; + +namespace OnixLabs.Security.Cryptography; + +public sealed partial class EcdsaPublicKey +{ + /// + /// Create a new instance from the specified value. + /// + /// The value from which to create a new instance. + /// Returns a new instance from the specified value. + public static implicit operator EcdsaPublicKey(byte[] value) => new(value); + + /// + /// Create a new instance from the specified value. + /// + /// The value from which to create a new instance. + /// Returns a new instance from the specified value. + public static implicit operator EcdsaPublicKey(ReadOnlySpan value) => new(value); +} diff --git a/OnixLabs.Security.Cryptography/Extensions.HashAlgorithm.cs b/OnixLabs.Security.Cryptography/Extensions.HashAlgorithm.cs index 8088217..46b7191 100644 --- a/OnixLabs.Security.Cryptography/Extensions.HashAlgorithm.cs +++ b/OnixLabs.Security.Cryptography/Extensions.HashAlgorithm.cs @@ -29,6 +29,16 @@ namespace OnixLabs.Security.Cryptography; [EditorBrowsable(EditorBrowsableState.Never)] public static class HashAlgorithmExtensions { + /// + /// Computes the hash value for the specified byte array. + /// + /// The which will be used to compute a hash value. + /// The input data to compute the hash for. + /// The number of rounds that the input data should be hashed. + /// Returns the computed hash value. + public static byte[] ComputeHash(this HashAlgorithm algorithm, IBinaryConvertible data, int rounds = 1) => + algorithm.ComputeHash(new MemoryStream(data.ToByteArray()), rounds); + /// /// Computes the hash value for the specified byte array. /// @@ -78,6 +88,17 @@ public static byte[] ComputeHash(this HashAlgorithm algorithm, Stream stream, in return data; } + /// + /// Asynchronously computes the hash value for the specified object. + /// + /// The which will be used to compute a hash value. + /// The input data to compute the hash for. + /// The number of rounds that the input data should be hashed. + /// The token to monitor for cancellation requests. + /// Returns a task that represents the asynchronous compute hash operation and wraps the computed hash value. + public static async Task ComputeHashAsync(this HashAlgorithm algorithm, IBinaryConvertible data, int rounds = 1, CancellationToken token = default) => + await algorithm.ComputeHashAsync(new MemoryStream(data.ToByteArray()), rounds, token); + /// /// Asynchronously computes the hash value for the specified object. /// diff --git a/OnixLabs.Security.Cryptography/Extensions.cs b/OnixLabs.Security.Cryptography/Extensions.cs new file mode 100644 index 0000000..4db8b1b --- /dev/null +++ b/OnixLabs.Security.Cryptography/Extensions.cs @@ -0,0 +1,54 @@ +// Copyright 2020 ONIXLabs +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +using System; +using System.ComponentModel; + +namespace OnixLabs.Security.Cryptography; + +/// +/// Provides extension methods for byte arrays and read-only spans. +/// +// ReSharper disable UnusedMethodReturnValue.Global +[EditorBrowsable(EditorBrowsableState.Never)] +public static class Extensions +{ + /// + /// Converts the current into a new instance. + /// + /// The value to convert. + /// Returns a new instance. + public static Hash ToHash(this byte[] value) => value; + + /// + /// Converts the current into a new instance. + /// + /// The value to convert. + /// Returns a new instance. + public static Hash ToHash(this ReadOnlySpan value) => value; + + /// + /// Converts the current into a new instance. + /// + /// The value to convert. + /// Returns a new instance. + public static Secret ToSecret(this byte[] value) => value; + + /// + /// Converts the current into a new instance. + /// + /// The value to convert. + /// Returns a new instance. + public static Secret ToSecret(this ReadOnlySpan value) => value; +} diff --git a/OnixLabs.Security.Cryptography/Hash.Compute.cs b/OnixLabs.Security.Cryptography/Hash.Compute.cs index 76ef3e6..d4fcbbd 100644 --- a/OnixLabs.Security.Cryptography/Hash.Compute.cs +++ b/OnixLabs.Security.Cryptography/Hash.Compute.cs @@ -18,6 +18,7 @@ using System.Text; using System.Threading; using System.Threading.Tasks; +using OnixLabs.Core; namespace OnixLabs.Security.Cryptography; @@ -29,11 +30,8 @@ public readonly partial struct Hash /// The which will be used to compute the hash. /// The data from which to compute a hash. /// Returns a cryptographic hash of the specified data. - public static Hash Compute(HashAlgorithm algorithm, byte[] data) - { - byte[] value = algorithm.ComputeHash(data); - return new Hash(value); - } + public static Hash Compute(HashAlgorithm algorithm, byte[] data) => + algorithm.ComputeHash(data); /// /// Computes the hash of the specified data, using the specified . @@ -42,11 +40,8 @@ public static Hash Compute(HashAlgorithm algorithm, byte[] data) /// The data from which to compute a hash. /// The number of rounds that the input data should be hashed. /// Returns a cryptographic hash of the specified data. - public static Hash Compute(HashAlgorithm algorithm, byte[] data, int rounds) - { - byte[] value = algorithm.ComputeHash(data, rounds); - return new Hash(value); - } + public static Hash Compute(HashAlgorithm algorithm, byte[] data, int rounds) => + algorithm.ComputeHash(data, rounds); /// /// Computes the hash of the specified data, using the specified . @@ -56,11 +51,8 @@ public static Hash Compute(HashAlgorithm algorithm, byte[] data, int rounds) /// The offset into the from which to begin using data. /// The number of bytes in the array to use as data. /// Returns a cryptographic hash of the specified data. - public static Hash Compute(HashAlgorithm algorithm, byte[] data, int offset, int count) - { - byte[] value = algorithm.ComputeHash(data, offset, count); - return new Hash(value); - } + public static Hash Compute(HashAlgorithm algorithm, byte[] data, int offset, int count) => + algorithm.ComputeHash(data, offset, count); /// /// Computes the hash of the specified data, using the specified . @@ -71,11 +63,8 @@ public static Hash Compute(HashAlgorithm algorithm, byte[] data, int offset, int /// The number of bytes in the array to use as data. /// The number of rounds that the input data should be hashed. /// Returns a cryptographic hash of the specified data. - public static Hash Compute(HashAlgorithm algorithm, byte[] data, int offset, int count, int rounds) - { - byte[] value = algorithm.ComputeHash(data, offset, count, rounds); - return new Hash(value); - } + public static Hash Compute(HashAlgorithm algorithm, byte[] data, int offset, int count, int rounds) => + algorithm.ComputeHash(data, offset, count, rounds); /// /// Computes the hash of the specified , using the specified . @@ -83,11 +72,8 @@ public static Hash Compute(HashAlgorithm algorithm, byte[] data, int offset, int /// The which will be used to compute the hash. /// The data from which to compute a hash. /// Returns a cryptographic hash of the specified data. - public static Hash Compute(HashAlgorithm algorithm, Stream stream) - { - byte[] value = algorithm.ComputeHash(stream); - return new Hash(value); - } + public static Hash Compute(HashAlgorithm algorithm, Stream stream) => + algorithm.ComputeHash(stream); /// /// Computes the hash of the specified , using the specified . @@ -96,11 +82,18 @@ public static Hash Compute(HashAlgorithm algorithm, Stream stream) /// The data from which to compute a hash. /// The number of rounds that the input data should be hashed. /// Returns a cryptographic hash of the specified data. - public static Hash Compute(HashAlgorithm algorithm, Stream stream, int rounds) - { - byte[] value = algorithm.ComputeHash(stream, rounds); - return new Hash(value); - } + public static Hash Compute(HashAlgorithm algorithm, Stream stream, int rounds) => + algorithm.ComputeHash(stream, rounds); + + /// + /// Computes the hash of the specified data, using the specified . + /// + /// The which will be used to compute the hash. + /// The data from which to compute a hash. + /// The number of rounds that the input data should be hashed. + /// Returns a cryptographic hash of the specified data. + public static Hash Compute(HashAlgorithm algorithm, IBinaryConvertible data, int rounds = 1) => + algorithm.ComputeHash(data, rounds); /// /// Computes the hash value for the specified . @@ -110,11 +103,8 @@ public static Hash Compute(HashAlgorithm algorithm, Stream stream, int rounds) /// The which will be used to convert the specified . /// The number of rounds that the input data should be hashed. /// Returns a cryptographic hash of the specified data. - public static Hash Compute(HashAlgorithm algorithm, ReadOnlySpan data, Encoding? encoding = null, int rounds = 1) - { - byte[] value = algorithm.ComputeHash(data, encoding, rounds); - return new Hash(value); - } + public static Hash Compute(HashAlgorithm algorithm, ReadOnlySpan data, Encoding? encoding = null, int rounds = 1) => + algorithm.ComputeHash(data, encoding, rounds); /// /// Asynchronously computes the hash of the specified data, using the specified . @@ -123,11 +113,8 @@ public static Hash Compute(HashAlgorithm algorithm, ReadOnlySpan data, Enc /// The data from which to compute a hash. /// The token to monitor for cancellation requests. /// Returns a cryptographic hash of the specified data. - public static async Task ComputeAsync(HashAlgorithm algorithm, Stream stream, CancellationToken token = default) - { - byte[] value = await algorithm.ComputeHashAsync(stream, token); - return new Hash(value); - } + public static async Task ComputeAsync(HashAlgorithm algorithm, Stream stream, CancellationToken token = default) => + await algorithm.ComputeHashAsync(stream, token); /// /// Asynchronously computes the hash of the specified data, using the specified . @@ -137,9 +124,17 @@ public static async Task ComputeAsync(HashAlgorithm algorithm, Stream stre /// The number of rounds that the input data should be hashed. /// The token to monitor for cancellation requests. /// Returns a cryptographic hash of the specified data. - public static async Task ComputeAsync(HashAlgorithm algorithm, Stream stream, int rounds, CancellationToken token = default) - { - byte[] value = await algorithm.ComputeHashAsync(stream, rounds, token); - return new Hash(value); - } + public static async Task ComputeAsync(HashAlgorithm algorithm, Stream stream, int rounds, CancellationToken token = default) => + await algorithm.ComputeHashAsync(stream, rounds, token); + + /// + /// Asynchronously computes the hash of the specified data, using the specified . + /// + /// The which will be used to compute the hash. + /// The input data to compute the hash for. + /// The number of rounds that the input data should be hashed. + /// The token to monitor for cancellation requests. + /// Returns a cryptographic hash of the specified data. + public static async Task ComputeAsync(HashAlgorithm algorithm, IBinaryConvertible data, int rounds = 1, CancellationToken token = default) => + await algorithm.ComputeHashAsync(data, rounds, token); } diff --git a/OnixLabs.Security.Cryptography/Hash.Convertible.cs b/OnixLabs.Security.Cryptography/Hash.Convertible.cs new file mode 100644 index 0000000..fe16187 --- /dev/null +++ b/OnixLabs.Security.Cryptography/Hash.Convertible.cs @@ -0,0 +1,34 @@ +// Copyright 2020 ONIXLabs +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +using System; + +namespace OnixLabs.Security.Cryptography; + +public readonly partial struct Hash +{ + /// + /// Create a new instance from the specified value. + /// + /// The value from which to create a new instance. + /// Returns a new instance from the specified value. + public static implicit operator Hash(byte[] value) => new(value); + + /// + /// Create a new instance from the specified value. + /// + /// The value from which to create a new instance. + /// Returns a new instance from the specified value. + public static implicit operator Hash(ReadOnlySpan value) => new(value); +} diff --git a/OnixLabs.Security.Cryptography/IPrivateKeyImportablePkcs8.cs b/OnixLabs.Security.Cryptography/IPrivateKeyImportablePkcs8.cs index a3cbfc4..4792d20 100644 --- a/OnixLabs.Security.Cryptography/IPrivateKeyImportablePkcs8.cs +++ b/OnixLabs.Security.Cryptography/IPrivateKeyImportablePkcs8.cs @@ -13,6 +13,7 @@ // limitations under the License. using System; +using OnixLabs.Core; namespace OnixLabs.Security.Cryptography; @@ -37,6 +38,21 @@ public interface IPrivateKeyImportablePkcs8 where T : PrivateKey /// Returns a new cryptographic private key from the imported data. public static abstract T ImportPkcs8PrivateKey(ReadOnlySpan data, out int bytesRead); + /// + /// Imports the cryptographic private key data in PKCS #8 format. + /// + /// The cryptographic private key data to import. + /// Returns a new cryptographic private key from the imported data. + public static abstract T ImportPkcs8PrivateKey(IBinaryConvertible data); + + /// + /// Imports the cryptographic private key data in PKCS #8 format. + /// + /// The cryptographic private key data to import. + /// The number of bytes read from the input data. + /// Returns a new cryptographic private key from the imported data. + public static abstract T ImportPkcs8PrivateKey(IBinaryConvertible data, out int bytesRead); + /// /// Imports the cryptographic private key data in encrypted PKCS #8 format. /// @@ -53,4 +69,21 @@ public interface IPrivateKeyImportablePkcs8 where T : PrivateKey /// The number of bytes read from the input data. /// Returns a new cryptographic private key from the imported data. public static abstract T ImportPkcs8PrivateKey(ReadOnlySpan data, ReadOnlySpan password, out int bytesRead); + + /// + /// Imports the cryptographic private key data in encrypted PKCS #8 format. + /// + /// The cryptographic private key data to import. + /// The password required for password based decryption. + /// Returns a new cryptographic private key from the imported data. + public static abstract T ImportPkcs8PrivateKey(IBinaryConvertible data, ReadOnlySpan password); + + /// + /// Imports the cryptographic private key data in encrypted PKCS #8 format. + /// + /// The cryptographic private key data to import. + /// The password required for password based decryption. + /// The number of bytes read from the input data. + /// Returns a new cryptographic private key from the imported data. + public static abstract T ImportPkcs8PrivateKey(IBinaryConvertible data, ReadOnlySpan password, out int bytesRead); } diff --git a/OnixLabs.Security.Cryptography/OnixLabs.Security.Cryptography.csproj b/OnixLabs.Security.Cryptography/OnixLabs.Security.Cryptography.csproj index 5455bac..8b0d820 100644 --- a/OnixLabs.Security.Cryptography/OnixLabs.Security.Cryptography.csproj +++ b/OnixLabs.Security.Cryptography/OnixLabs.Security.Cryptography.csproj @@ -4,13 +4,13 @@ OnixLabs.Security.Cryptography ONIXLabs ONIXLabs Cryptography API for .NET - 8.13.0 + 8.14.0 en enable true Copyright © ONIXLabs 2020 https://github.com/onix-labs/onixlabs-dotnet - 8.13.0 + 8.14.0 12 diff --git a/OnixLabs.Security.Cryptography/PrivateKey.To.cs b/OnixLabs.Security.Cryptography/PrivateKey.To.cs index 91434d2..3b2f0da 100644 --- a/OnixLabs.Security.Cryptography/PrivateKey.To.cs +++ b/OnixLabs.Security.Cryptography/PrivateKey.To.cs @@ -26,6 +26,13 @@ public abstract partial class PrivateKey /// Return the underlying representation of the current instance. public byte[] ToByteArray() => KeyData.Copy(); + /// + /// Creates a new from the current instance. + /// + /// The name of the algorithm that was used to produce the associated private key. + /// Returns a new from the current instance. + public NamedPrivateKey ToNamedPrivateKey(string algorithmName) => new(this, algorithmName); + /// /// Returns a that represents the current object. /// diff --git a/OnixLabs.Security.Cryptography/PublicKey.To.cs b/OnixLabs.Security.Cryptography/PublicKey.To.cs index 96e1293..c28f04a 100644 --- a/OnixLabs.Security.Cryptography/PublicKey.To.cs +++ b/OnixLabs.Security.Cryptography/PublicKey.To.cs @@ -26,6 +26,13 @@ public abstract partial class PublicKey /// Return the underlying representation of the current instance. public byte[] ToByteArray() => KeyData.Copy(); + /// + /// Creates a new from the current instance. + /// + /// The name of the algorithm that was used to produce the associated public key. + /// Returns a new from the current instance. + public NamedPublicKey ToNamedPublicKey(string algorithmName) => new(this, algorithmName); + /// /// Returns a that represents the current object. /// diff --git a/OnixLabs.Security.Cryptography/RsaPrivateKey.Convertible.cs b/OnixLabs.Security.Cryptography/RsaPrivateKey.Convertible.cs new file mode 100644 index 0000000..92262a1 --- /dev/null +++ b/OnixLabs.Security.Cryptography/RsaPrivateKey.Convertible.cs @@ -0,0 +1,34 @@ +// Copyright 2020 ONIXLabs +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +using System; + +namespace OnixLabs.Security.Cryptography; + +public sealed partial class RsaPrivateKey +{ + /// + /// Create a new instance from the specified value. + /// + /// The value from which to create a new instance. + /// Returns a new instance from the specified value. + public static implicit operator RsaPrivateKey(byte[] value) => new(value); + + /// + /// Create a new instance from the specified value. + /// + /// The value from which to create a new instance. + /// Returns a new instance from the specified value. + public static implicit operator RsaPrivateKey(ReadOnlySpan value) => new(value); +} diff --git a/OnixLabs.Security.Cryptography/RsaPrivateKey.Import.cs b/OnixLabs.Security.Cryptography/RsaPrivateKey.Import.cs index e8eadf1..ae95cb7 100644 --- a/OnixLabs.Security.Cryptography/RsaPrivateKey.Import.cs +++ b/OnixLabs.Security.Cryptography/RsaPrivateKey.Import.cs @@ -14,6 +14,7 @@ using System; using System.Security.Cryptography; +using OnixLabs.Core; namespace OnixLabs.Security.Cryptography; @@ -24,7 +25,8 @@ public sealed partial class RsaPrivateKey /// /// The cryptographic private key data to import. /// Returns a new instance from the imported cryptographic private key data. - public static RsaPrivateKey ImportPkcs8PrivateKey(ReadOnlySpan data) => ImportPkcs8PrivateKey(data, out int _); + public static RsaPrivateKey ImportPkcs8PrivateKey(ReadOnlySpan data) => + ImportPkcs8PrivateKey(data, out int _); /// /// Imports the RSA cryptographic private key data in PKCS #8 format. @@ -40,13 +42,31 @@ public static RsaPrivateKey ImportPkcs8PrivateKey(ReadOnlySpan data, out i return new RsaPrivateKey(keyData); } + /// + /// Imports the RSA cryptographic private key data in PKCS #8 format. + /// + /// The cryptographic private key data to import. + /// Returns a new instance from the imported cryptographic private key data. + public static RsaPrivateKey ImportPkcs8PrivateKey(IBinaryConvertible data) => + ImportPkcs8PrivateKey(data, out int _); + + /// + /// Imports the RSA cryptographic private key data in PKCS #8 format. + /// + /// The cryptographic private key data to import. + /// The number of bytes read from the input data. + /// Returns a new instance from the imported cryptographic private key data. + public static RsaPrivateKey ImportPkcs8PrivateKey(IBinaryConvertible data, out int bytesRead) => + ImportPkcs8PrivateKey(data.ToByteArray(), out bytesRead); + /// /// Imports the RSA cryptographic private key data in encrypted PKCS #8 format. /// /// The cryptographic private key data to import. /// The password required for password based decryption. /// Returns a new instance from the imported cryptographic private key data. - public static RsaPrivateKey ImportPkcs8PrivateKey(ReadOnlySpan data, ReadOnlySpan password) => ImportPkcs8PrivateKey(data, password, out int _); + public static RsaPrivateKey ImportPkcs8PrivateKey(ReadOnlySpan data, ReadOnlySpan password) => + ImportPkcs8PrivateKey(data, password, out int _); /// /// Imports the RSA cryptographic private key data in encrypted PKCS #8 format. @@ -63,6 +83,25 @@ public static RsaPrivateKey ImportPkcs8PrivateKey(ReadOnlySpan data, ReadO return new RsaPrivateKey(keyData); } + /// + /// Imports the RSA cryptographic private key data in encrypted PKCS #8 format. + /// + /// The cryptographic private key data to import. + /// The password required for password based decryption. + /// Returns a new instance from the imported cryptographic private key data. + public static RsaPrivateKey ImportPkcs8PrivateKey(IBinaryConvertible data, ReadOnlySpan password) => + ImportPkcs8PrivateKey(data, password, out int _); + + /// + /// Imports the RSA cryptographic private key data in encrypted PKCS #8 format. + /// + /// The cryptographic private key data to import. + /// The password required for password based decryption. + /// The number of bytes read from the input data. + /// Returns a new instance from the imported cryptographic private key data. + public static RsaPrivateKey ImportPkcs8PrivateKey(IBinaryConvertible data, ReadOnlySpan password, out int bytesRead) => + ImportPkcs8PrivateKey(data.ToByteArray(), password, out bytesRead); + /// /// Imports the key data into a new instance. /// diff --git a/OnixLabs.Security.Cryptography/RsaPublicKey.Convertible.cs b/OnixLabs.Security.Cryptography/RsaPublicKey.Convertible.cs new file mode 100644 index 0000000..ebd2aa9 --- /dev/null +++ b/OnixLabs.Security.Cryptography/RsaPublicKey.Convertible.cs @@ -0,0 +1,34 @@ +// Copyright 2020 ONIXLabs +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +using System; + +namespace OnixLabs.Security.Cryptography; + +public sealed partial class RsaPublicKey +{ + /// + /// Create a new instance from the specified value. + /// + /// The value from which to create a new instance. + /// Returns a new instance from the specified value. + public static implicit operator RsaPublicKey(byte[] value) => new(value); + + /// + /// Create a new instance from the specified value. + /// + /// The value from which to create a new instance. + /// Returns a new instance from the specified value. + public static implicit operator RsaPublicKey(ReadOnlySpan value) => new(value); +} diff --git a/OnixLabs.Security.Cryptography/Secret.Convertible.cs b/OnixLabs.Security.Cryptography/Secret.Convertible.cs index 0ed6d34..1ba275c 100644 --- a/OnixLabs.Security.Cryptography/Secret.Convertible.cs +++ b/OnixLabs.Security.Cryptography/Secret.Convertible.cs @@ -12,6 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. +using System; using System.Buffers; using System.Text; @@ -19,6 +20,20 @@ namespace OnixLabs.Security.Cryptography; public readonly partial struct Secret { + /// + /// Create a new instance from the specified value. + /// + /// The value from which to create a new instance. + /// Returns a new instance from the specified value. + public static implicit operator Secret(byte[] value) => new(value); + + /// + /// Create a new instance from the specified value. + /// + /// The value from which to create a new instance. + /// Returns a new instance from the specified value. + public static implicit operator Secret(ReadOnlySpan value) => new(value); + /// /// Create a new instance from the specified value. /// The value will be encoded using the encoding. @@ -36,7 +51,7 @@ public readonly partial struct Secret public static implicit operator Secret(char[] value) => new(Encoding.UTF8.GetBytes(value)); /// - /// Create a new instance from the specified value. + /// Create a new instance from the specified value. /// The value will be encoded using the encoding. /// /// The value from which to create a new instance. diff --git a/OnixLabs.Security.UnitTests/OnixLabs.Security.UnitTests.csproj b/OnixLabs.Security.UnitTests/OnixLabs.Security.UnitTests.csproj index 523a9bf..bb58237 100644 --- a/OnixLabs.Security.UnitTests/OnixLabs.Security.UnitTests.csproj +++ b/OnixLabs.Security.UnitTests/OnixLabs.Security.UnitTests.csproj @@ -10,10 +10,16 @@ - - - - + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + diff --git a/OnixLabs.Security.UnitTests/SecurityTokenBuilderTests.cs b/OnixLabs.Security.UnitTests/SecurityTokenBuilderTests.cs index 2bfecdd..bd4d876 100644 --- a/OnixLabs.Security.UnitTests/SecurityTokenBuilderTests.cs +++ b/OnixLabs.Security.UnitTests/SecurityTokenBuilderTests.cs @@ -16,6 +16,48 @@ namespace OnixLabs.Security.UnitTests; public sealed class SecurityTokenBuilderTests { + [Fact(DisplayName = "SecurityTokenBuilder should be constructable from pseudo-random number generator with length")] + public void SecurityTokenBuilderShouldBeConstructableFromPseudoRandomNumberGeneratorWithLength() + { + // Given / When + // ReSharper disable once JoinDeclarationAndInitializer + SecurityTokenBuilder? builder; + + // When + builder = SecurityTokenBuilder.CreatePseudoRandom(32); + + // Then + Assert.NotNull(builder); + } + + [Fact(DisplayName = "SecurityTokenBuilder should be constructable from pseudo-random number generator with length and random instance")] + public void SecurityTokenBuilderShouldBeConstructableFromPseudoRandomNumberGeneratorWithLengthAndRandomInstance() + { + // Given / When + // ReSharper disable once JoinDeclarationAndInitializer + SecurityTokenBuilder? builder; + + // When + builder = SecurityTokenBuilder.CreatePseudoRandom(32, Random.Shared); + + // Then + Assert.NotNull(builder); + } + + [Fact(DisplayName = "SecurityTokenBuilder should be constructable from secure-random number generator with length")] + public void SecurityTokenBuilderShouldBeConstructableFromSecureRandomNumberGeneratorWithLength() + { + // Given / When + // ReSharper disable once JoinDeclarationAndInitializer + SecurityTokenBuilder? builder; + + // When + builder = SecurityTokenBuilder.CreateSecureRandom(32); + + // Then + Assert.NotNull(builder); + } + [Theory(DisplayName = "SecurityTokenBuilder.UseCharacters should produce the expected result")] [InlineData(1, 0, "3")] [InlineData(1, 4, "!")] diff --git a/OnixLabs.Security.UnitTests/SecurityTokenTests.cs b/OnixLabs.Security.UnitTests/SecurityTokenTests.cs index b0d70e4..70940a9 100644 --- a/OnixLabs.Security.UnitTests/SecurityTokenTests.cs +++ b/OnixLabs.Security.UnitTests/SecurityTokenTests.cs @@ -104,4 +104,21 @@ public void DifferentSecurityTokenValuesShouldProduceDifferentSecurityTokenCodes // Then Assert.NotEqual(leftHashCode, rightHashCode); } + + [Theory(DisplayName = "SecurityToken.Length should produce the expected result")] + [InlineData("a", 1)] + [InlineData("ab", 2)] + [InlineData("abcd", 4)] + [InlineData("abcdefghijklmnopqrstuvwxyz", 26)] + public void SecurityTokenLengthShouldProduceExpectedResult(string value, int expected) + { + // Given + SecurityToken token = new(value); + + // When + int actual = token.Length; + + // Then + Assert.Equal(expected, actual); + } } diff --git a/OnixLabs.Security/OnixLabs.Security.csproj b/OnixLabs.Security/OnixLabs.Security.csproj index 4c62fd3..1232ad8 100644 --- a/OnixLabs.Security/OnixLabs.Security.csproj +++ b/OnixLabs.Security/OnixLabs.Security.csproj @@ -4,13 +4,13 @@ OnixLabs.Security ONIXLabs ONIXLabs Security API for .NET - 8.13.0 + 8.14.0 en enable true Copyright © ONIXLabs 2020 https://github.com/onix-labs/onixlabs-dotnet - 8.13.0 + 8.14.0 12