diff --git a/.github/workflows/dotnet.yml b/.github/workflows/dotnet.yml index 3afdb11..5b1cb93 100644 --- a/.github/workflows/dotnet.yml +++ b/.github/workflows/dotnet.yml @@ -7,6 +7,80 @@ on: branches: - main jobs: + label: + runs-on: ubuntu-latest + steps: + - name: Apply Label + uses: actions/github-script@v6 + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + script: >- + const prefixes = [ + 'INFRA:', + 'PROVISIONS:', + 'RELEASES:', + 'DATA:', + 'BROKERS:', + 'FOUNDATIONS:', + 'PROCESSINGS:', + 'ORCHESTRATIONS:', + 'COORDINATIONS:', + 'MANAGEMENTS:', + 'AGGREGATIONS:', + 'CONTROLLERS:', + 'CLIENTS:', + 'EXPOSERS:', + 'PROVIDERS:', + 'BASE:', + 'COMPONENTS:', + 'VIEWS:', + 'PAGES:', + 'ACCEPTANCE:', + 'INTEGRATIONS:', + 'CODE RUB:', + 'MINOR FIX:', + 'MEDIUM FIX:', + 'MAJOR FIX:', + 'DOCUMENTATION:', + 'CONFIG:', + 'STANDARD:', + 'DESIGN:', + 'BUSINESS:' + ]; + + + const pullRequest = context.payload.pull_request; + + + if (!pullRequest) { + console.log('No pull request context available.'); + return; + } + + + const title = context.payload.pull_request.title; + + const existingLabels = context.payload.pull_request.labels.map(label => label.name); + + + for (const prefix of prefixes) { + if (title.startsWith(prefix)) { + const label = prefix.slice(0, -1); + if (!existingLabels.includes(label)) { + console.log(`Applying label: ${label}`); + await github.rest.issues.addLabels({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: context.payload.pull_request.number, + labels: [label] + }); + } + break; + } + } + permissions: + contents: read + pull-requests: write build: runs-on: ubuntu-latest steps: @@ -15,7 +89,7 @@ jobs: - name: Setup .Net uses: actions/setup-dotnet@v3 with: - dotnet-version: 7.0.201 + dotnet-version: 9.0.100 - name: Restore run: dotnet restore - name: Build @@ -53,7 +127,7 @@ jobs: sudo apt-get install xmlstarlet - version_number=$(xmlstarlet sel -t -v "//Version" -n ADotNet/ADotNet.csproj) + version_number=$(xmlstarlet sel -t -v "//Version" -n Xeption/Xeption.csproj) echo "$version_number" @@ -72,7 +146,7 @@ jobs: sudo apt-get install xmlstarlet - package_release_notes=$(xmlstarlet sel -t -v "//PackageReleaseNotes" -n ADotNet/ADotNet.csproj) + package_release_notes=$(xmlstarlet sel -t -v "//PackageReleaseNotes" -n Xeption/Xeption.csproj) echo "$package_release_notes" @@ -114,7 +188,7 @@ jobs: - name: Setup .Net uses: actions/setup-dotnet@v3 with: - dotnet-version: 7.0.201 + dotnet-version: 9.0.100 - name: Restore run: dotnet restore - name: Build diff --git a/Xeption.Infrastructure.Build/Program.cs b/Xeption.Infrastructure.Build/Program.cs index c0314b8..47b452b 100644 --- a/Xeption.Infrastructure.Build/Program.cs +++ b/Xeption.Infrastructure.Build/Program.cs @@ -16,6 +16,7 @@ internal class Program static void Main(string[] args) { string branchName = "main"; + string dotNetVersion = "9.0.100"; var adotNetClient = new ADotNetClient(); var githubPipeline = new GithubPipeline @@ -37,6 +38,10 @@ static void Main(string[] args) Jobs = new Dictionary { + { + "label", + new LabelJobV2(runsOn: BuildMachines.UbuntuLatest) + }, { "build", new Job @@ -56,7 +61,7 @@ static void Main(string[] args) With = new TargetDotNetVersionV3 { - DotNetVersion = "7.0.201" + DotNetVersion = dotNetVersion } }, @@ -88,9 +93,10 @@ static void Main(string[] args) }, { "publish", - new PublishJob( + new PublishJobV2( runsOn: BuildMachines.UbuntuLatest, dependsOn: "add_tag", + dotNetVersion: dotNetVersion, nugetApiKey: "${{ secrets.NUGET_ACCESS }}") } } diff --git a/Xeption.Infrastructure.Build/Xeption.Infrastructure.Build.csproj b/Xeption.Infrastructure.Build/Xeption.Infrastructure.Build.csproj index 1920e29..5deccf8 100644 --- a/Xeption.Infrastructure.Build/Xeption.Infrastructure.Build.csproj +++ b/Xeption.Infrastructure.Build/Xeption.Infrastructure.Build.csproj @@ -2,12 +2,12 @@ Exe - net6.0 + net9.0 disable - + diff --git a/Xeption.Tests/Xeption.Tests.csproj b/Xeption.Tests/Xeption.Tests.csproj index effd22f..aefcee6 100644 --- a/Xeption.Tests/Xeption.Tests.csproj +++ b/Xeption.Tests/Xeption.Tests.csproj @@ -1,7 +1,7 @@ - + - net6.0 + net9.0 disable false disable @@ -9,16 +9,16 @@ - - + + - - - + + + runtime; build; native; contentfiles; analyzers; buildtransitive all - + runtime; build; native; contentfiles; analyzers; buildtransitive all diff --git a/Xeption.Tests/XeptionAssertionTests.Logic.BeEquivalentTo.cs b/Xeption.Tests/XeptionAssertionTests.Logic.BeEquivalentTo.cs index de6756c..7b93614 100644 --- a/Xeption.Tests/XeptionAssertionTests.Logic.BeEquivalentTo.cs +++ b/Xeption.Tests/XeptionAssertionTests.Logic.BeEquivalentTo.cs @@ -14,7 +14,7 @@ namespace Xeptions.Tests { public partial class XeptionAssertionTests { - [Fact(DisplayName = "01.0 - BeEquivalentToShouldPassIfNullExceptionsMatch")] + [Fact(DisplayName = "01.0 - Level 0 - BeEquivalentToShouldPassIfNullExceptionsMatch")] public void BeEquivalentToShouldPassIfNullExceptionsMatch() { // given @@ -25,7 +25,7 @@ public void BeEquivalentToShouldPassIfNullExceptionsMatch() actualException.Should().BeEquivalentTo(expectedException); } - [Fact(DisplayName = "02.1 - BeEquivalentToShouldPassIfExceptionsMatch")] + [Fact(DisplayName = "02.1 - Level 0 - BeEquivalentToShouldPassIfExceptionsMatch")] public void BeEquivalentToShouldPassIfExceptionsMatch() { // given @@ -37,7 +37,7 @@ public void BeEquivalentToShouldPassIfExceptionsMatch() actualException.Should().BeEquivalentTo(expectedException); } - [Fact(DisplayName = "02.2 - BeEquivalentToShouldFailIfExceptionsDontMatchOnType")] + [Fact(DisplayName = "02.2 - Level 0 - BeEquivalentToShouldFailIfExceptionsDontMatchOnType")] public void BeEquivalentToShouldFailIfExceptionsDontMatchOnType() { // given @@ -46,8 +46,8 @@ public void BeEquivalentToShouldFailIfExceptionsDontMatchOnType() var actualException = new Exception(message: randomMessage); string expectedMessage = - $"Expected exception type to be \"{expectedException.GetType().FullName}\", " + - $"but found \"{actualException.GetType().FullName}\"."; + $"Expected exception to be \"{expectedException.GetType().FullName}\", " + + $"but found \"{actualException.GetType().FullName}\""; // when Action assertAction = () => @@ -60,7 +60,7 @@ public void BeEquivalentToShouldFailIfExceptionsDontMatchOnType() actualError.Message.Should().Contain(expectedMessage); } - [Fact(DisplayName = "02.3 - BeEquivalentToShouldFailIfExceptionMessagesDontMatch")] + [Fact(DisplayName = "02.3 - Level 0 - BeEquivalentToShouldFailIfExceptionMessagesDontMatch")] public void BeEquivalentToShouldFailIfExceptionMessagesDontMatch() { // given @@ -82,7 +82,7 @@ public void BeEquivalentToShouldFailIfExceptionMessagesDontMatch() actualError.Message.Should().Contain(expectedMessage); } - [Fact(DisplayName = "02.4 - BeEquivalentToShouldPassIfExceptionDataMatch")] + [Fact(DisplayName = "02.4 - Level 0 - BeEquivalentToShouldPassIfExceptionDataMatch")] public void BeEquivalentToShouldPassIfExceptionDataMatch() { // given @@ -109,7 +109,7 @@ public void BeEquivalentToShouldPassIfExceptionDataMatch() actualException.Should().BeEquivalentTo(expectedException); } - [Fact(DisplayName = "02.5 - BeEquivalentToShouldFailIfExceptionDataDontMatch")] + [Fact(DisplayName = "02.5 - Level 0 - BeEquivalentToShouldFailIfExceptionDataDontMatch")] public void BeEquivalentToShouldFailIfExceptionDataDontMatch() { // given @@ -155,21 +155,21 @@ public void BeEquivalentToShouldFailIfExceptionDataDontMatch() expectedError.AppendLine($"Expected exception to:"); expectedError.AppendLine( - $"- have an expected data item count to be {expectedException.Data.Count}, " + - $"but found {actualException.Data.Count}."); + $"- have a data count of {expectedException.Data.Count}, " + + $"but found {actualException.Data.Count}"); expectedError.AppendLine( - $"- contain key '{expectedDataOne.Key}' with value(s) [{expectedDataOne.Value[0]}]."); + $"- NOT contain key \"{actualData.Key}\""); expectedError.AppendLine( - $"- contain key '{expectedDataTwo.Key}' with value(s) [{expectedDataTwo.Value[0]}]."); + $"- contain key \"{expectedDataOne.Key}\" with value(s) ['{expectedDataOne.Value[0]}']"); expectedError.AppendLine( - $"- NOT contain key '{actualData.Key}'."); + $"- contain key \"{expectedDataTwo.Key}\" with value(s) ['{expectedDataTwo.Value[0]}']"); expectedError.AppendLine( - $"- have key '{mutualKey}' with value(s) [{expectedDataSameKeyName.Value[0]}], " + - $"but found value(s) [{actualDataSameKeyName.Value[0]}]."); + $"- have key \"{mutualKey}\" with value(s) ['{expectedDataSameKeyName.Value[0]}'], " + + $"but found value(s) ['{actualDataSameKeyName.Value[0]}']"); // when Action assertAction = () => @@ -179,10 +179,10 @@ public void BeEquivalentToShouldFailIfExceptionDataDontMatch() Assert.Throws(assertAction); //then - actualError.Message.Should().BeEquivalentTo(expectedError.ToString()); + actualError.Message.Should().BeEquivalentTo(expectedError.ToString().Trim()); } - [Fact(DisplayName = "03.1 - BeEquivalentToShouldPassIfInnerExceptionsMatch")] + [Fact(DisplayName = "03.1 - Level 0 - BeEquivalentToShouldPassIfInnerExceptionsMatch")] public void BeEquivalentToShouldPassIfInnerExceptionsMatchOnType() { // given @@ -202,7 +202,7 @@ public void BeEquivalentToShouldPassIfInnerExceptionsMatchOnType() actualException.Should().BeEquivalentTo(expectedException); } - [Fact(DisplayName = "03.2 - BeEquivalentToShouldFailIfInnerExceptionsTypeDontMatch")] + [Fact(DisplayName = "03.2 - Level 1 - BeEquivalentToShouldFailIfInnerExceptionsTypeDontMatch")] public void BeEquivalentToShouldFailIfInnerExceptionsTypeDontMatch() { // given @@ -219,8 +219,8 @@ public void BeEquivalentToShouldFailIfInnerExceptionsTypeDontMatch() innerException: actualInnerException); string expectedMessage = - $"Expected inner exception type to be \"{expectedInnerException.GetType().FullName}\", " + - $"but found \"{actualInnerException.GetType().FullName}\"."; + $"Expected inner exception (level 1) to be \"{expectedInnerException.GetType().FullName}\", " + + $"but found \"{actualInnerException.GetType().FullName}\""; // when Action assertAction = () => @@ -233,7 +233,7 @@ public void BeEquivalentToShouldFailIfInnerExceptionsTypeDontMatch() actualError.Message.Should().Contain(expectedMessage); } - [Fact(DisplayName = "03.3 - BeEquivalentToShouldFailIfInnerExceptionMessageDontMatch")] + [Fact(DisplayName = "03.3 - Level 1 - BeEquivalentToShouldFailIfInnerExceptionMessageDontMatch")] public void BeEquivalentToShouldFailIfInnerExceptionMessageDontMatch() { // given @@ -250,7 +250,7 @@ public void BeEquivalentToShouldFailIfInnerExceptionMessageDontMatch() innerException: actualInnerException); string expectedMessage = - $"Expected inner exception message to be \"{expectedInnerException.Message}\", " + + $"Expected inner exception (level 1) message to be \"{expectedInnerException.Message}\", " + $"but found \"{actualInnerException.Message}\""; // when @@ -264,7 +264,7 @@ public void BeEquivalentToShouldFailIfInnerExceptionMessageDontMatch() actualError.Message.Should().Contain(expectedMessage); } - [Fact(DisplayName = "03.4 - BeEquivalentToShouldPassIfInnerExceptionDataMatch")] + [Fact(DisplayName = "03.4 - Level 1 - BeEquivalentToShouldPassIfInnerExceptionDataMatch")] public void BeEquivalentToShouldPassIfInnerExceptionDataMatch() { // given @@ -299,8 +299,8 @@ public void BeEquivalentToShouldPassIfInnerExceptionDataMatch() actualException.Should().BeEquivalentTo(expectedException); } - [Fact(DisplayName = "03.5 - BeEquivalentToShouldFailIfInnerExceptionDataDontMatch")] - public void BeEquivalentToShouldFailIfAggregateInnerExceptionDataDontMatch() + [Fact(DisplayName = "03.5 - Level 1 - BeEquivalentToShouldFailIfInnerExceptionDataDontMatch")] + public void BeEquivalentToShouldFailIfInnerExceptionDataDontMatch() { // given string exceptionMessage = GetRandomString(); @@ -338,24 +338,24 @@ public void BeEquivalentToShouldFailIfAggregateInnerExceptionDataDontMatch() values: actualData.Value.ToArray()); var expectedError = new StringBuilder(); - expectedError.AppendLine($"Expected inner exception to:"); + expectedError.AppendLine($"Expected inner exception (level 1) to:"); expectedError.AppendLine( - $"- have an expected data item count to be {expectedInnerException.Data.Count}, " + - $"but found {actualInnerException.Data.Count}."); + $"- have a data count of {expectedInnerException.Data.Count}, " + + $"but found {actualInnerException.Data.Count}"); expectedError.AppendLine( - $"- contain key '{expectedDataOne.Key}' with value(s) [{expectedDataOne.Value[0]}]."); + $"- NOT contain key \"{actualData.Key}\""); expectedError.AppendLine( - $"- contain key '{expectedDataTwo.Key}' with value(s) [{expectedDataTwo.Value[0]}]."); + $"- contain key \"{expectedDataOne.Key}\" with value(s) ['{expectedDataOne.Value[0]}']"); expectedError.AppendLine( - $"- NOT contain key '{actualData.Key}'."); + $"- contain key \"{expectedDataTwo.Key}\" with value(s) ['{expectedDataTwo.Value[0]}']"); expectedError.AppendLine( - $"- have key '{mutualKey}' with value(s) [{expectedDataSameKeyName.Value[0]}], " + - $"but found value(s) [{actualDataSameKeyName.Value[0]}]."); + $"- have key \"{mutualKey}\" with value(s) ['{expectedDataSameKeyName.Value[0]}'], " + + $"but found value(s) ['{actualDataSameKeyName.Value[0]}']"); var expectedException = new Xeption( message: exceptionMessage, @@ -373,64 +373,64 @@ public void BeEquivalentToShouldFailIfAggregateInnerExceptionDataDontMatch() Assert.Throws(assertAction); //then - actualError.Message.Should().BeEquivalentTo(expectedError.ToString()); + actualError.Message.Should().BeEquivalentTo(expectedError.ToString().Trim()); } - [Fact(DisplayName = "04.1 - BeEquivalentToShouldPassIfAggregateExceptionsMatch")] - public void BeEquivalentToShouldPassIfAggregateExceptionsMatch() + [Fact(DisplayName = "04.1 - Level 2 - BeEquivalentToShouldPassIfInnerInnerExceptionsMatch")] + public void BeEquivalentToShouldPassIfInnerInnerExceptionsMatch() { // given string randomMessage = GetRandomString(); - Xeption expectedInnerException = new Xeption(message: randomMessage); - Xeption actualInnerException = new Xeption(message: randomMessage); - string aggregateExceptionMessage = GetRandomString(); + Xeption expectedInnerInnerException = new Xeption(message: randomMessage); + Xeption actualInnerInnerException = new Xeption(message: randomMessage); - AggregateException expectedAggregateException = new AggregateException( - message: aggregateExceptionMessage, - innerExceptions: new List { expectedInnerException }); + Xeption expectedInnerException = new Xeption( + message: randomMessage, + innerException: expectedInnerInnerException); - AggregateException actualAggregateException = new AggregateException( - message: aggregateExceptionMessage, - innerExceptions: new List { actualInnerException }); + Xeption actualInnerException = new Xeption( + message: randomMessage, + innerException: actualInnerInnerException); var expectedException = new Xeption( message: randomMessage, - innerException: expectedAggregateException); + innerException: expectedInnerException); var actualException = new Xeption( message: randomMessage, - innerException: actualAggregateException); + innerException: actualInnerException); // when then actualException.Should().BeEquivalentTo(expectedException); } - [Fact(DisplayName = "04.2 - BeEquivalentToShouldFailIfAggregateExceptionsDontMatchOnMessage")] - public void BeEquivalentToShouldFailIfAggregateExceptionsDontMatchOnMessage() + [Fact(DisplayName = "04.2 - Level 2 - BeEquivalentToShouldFailIfInnerInnerExceptionsTypeDontMatch")] + public void BeEquivalentToShouldFailIfInnerInnerExceptionsTypeDontMatch() { // given string randomMessage = GetRandomString(); - string aggregateExpectedMessage = GetRandomString(); - string aggregateActualMessage = GetRandomString(); + Xeption expectedInnerInnerException = new Xeption(message: randomMessage); + Exception actualInnerInnerException = new Exception(message: randomMessage); - AggregateException expectedAggregateException = new AggregateException( - message: aggregateExpectedMessage); + Xeption expectedInnerException = new Xeption( + message: randomMessage, + innerException: expectedInnerInnerException); - AggregateException actualAggregateException = new AggregateException( - message: aggregateActualMessage); + Xeption actualInnerException = new Xeption( + message: randomMessage, + innerException: actualInnerInnerException); var expectedException = new Xeption( message: randomMessage, - innerException: expectedAggregateException); + innerException: expectedInnerException); var actualException = new Xeption( message: randomMessage, - innerException: actualAggregateException); + innerException: actualInnerException); string expectedMessage = - $"Expected aggregate inner exception message to be " + - $"\"{expectedAggregateException.Message}\", " + - $"but found \"{actualAggregateException.Message}\"."; + $"Expected inner exception (level 2) to be \"{expectedInnerInnerException.GetType().FullName}\", " + + $"but found \"{actualInnerInnerException.GetType().FullName}\""; // when Action assertAction = () => @@ -443,33 +443,33 @@ public void BeEquivalentToShouldFailIfAggregateExceptionsDontMatchOnMessage() actualError.Message.Should().Contain(expectedMessage); } - [Fact(DisplayName = "04.3 - BeEquivalentToShouldFailIfAggregateInnerExceptionsMessagesDontMatch")] - public void BeEquivalentToShouldFailIfAggregateInnerExceptionsMessagesDontMatch() + [Fact(DisplayName = "04.3 - Level 2 - BeEquivalentToShouldFailIfInnerInnerExceptionMessageDontMatch")] + public void BeEquivalentToShouldFailIfInnerInnerExceptionMessageDontMatch() { // given string randomMessage = GetRandomString(); - Xeption expectedInnerExceptionOne = new Xeption(message: GetRandomString()); - Xeption expectedInnerExceptionTwo = new Xeption(message: GetRandomString()); + var expectedInnerInnerException = new Xeption(message: GetRandomString()); + var actualInnerInnerException = new Xeption(message: GetRandomString()); - AggregateException expectedAggregateException = new AggregateException( + Xeption expectedInnerException = new Xeption( message: randomMessage, - innerExceptions: new List { expectedInnerExceptionOne, expectedInnerExceptionTwo }); + innerException: expectedInnerInnerException); - AggregateException actualAggregateException = new AggregateException( - message: randomMessage); + Xeption actualInnerException = new Xeption( + message: randomMessage, + innerException: actualInnerInnerException); var expectedException = new Xeption( message: randomMessage, - innerException: expectedAggregateException); + innerException: expectedInnerException); var actualException = new Xeption( message: randomMessage, - innerException: actualAggregateException); + innerException: actualInnerException); string expectedMessage = - $"Expected aggregate inner exception message to be " + - $"\"{expectedAggregateException.Message}\", " + - $"but found \"{actualAggregateException.Message}\"."; + $"Expected inner exception (level 2) message to be \"{expectedInnerInnerException.Message}\", " + + $"but found \"{actualInnerInnerException.Message}\""; // when Action assertAction = () => @@ -482,59 +482,510 @@ public void BeEquivalentToShouldFailIfAggregateInnerExceptionsMessagesDontMatch( actualError.Message.Should().Contain(expectedMessage); } - [Fact(DisplayName = "04.3 - BeEquivalentToShouldFailIfAggregateInnerExceptionsDataDontMatch")] - public void BeEquivalentToShouldFailIfAggregateInnerExceptionsDataDontMatch() + [Fact(DisplayName = "04.4 - Level 2 - BeEquivalentToShouldPassIfInnerInnerExceptionDataMatch")] + public void BeEquivalentToShouldPassIfInnerInnerExceptionDataMatch() + { + // given + string exceptionMessage = GetRandomString(); + KeyValuePair> randomData = GenerateKeyValuePair(count: 1); + KeyValuePair> expectedData = randomData.DeepClone(); + KeyValuePair> actualData = randomData.DeepClone(); + + var expectedInnerInnerException = new Xeption( + message: exceptionMessage); + + expectedInnerInnerException.AddData( + key: expectedData.Key, + values: expectedData.Value.ToArray()); + + var actualInnerInnerException = new Xeption( + message: exceptionMessage); + + actualInnerInnerException.AddData( + key: actualData.Key, + values: actualData.Value.ToArray()); + + Xeption expectedInnerException = new Xeption( + message: exceptionMessage, + innerException: expectedInnerInnerException); + + Xeption actualInnerException = new Xeption( + message: exceptionMessage, + innerException: actualInnerInnerException); + + var expectedException = new Xeption( + message: exceptionMessage, + innerException: expectedInnerException); + + var actualException = new Xeption( + message: exceptionMessage, + innerException: actualInnerException); + + // when then + actualException.Should().BeEquivalentTo(expectedException); + } + + [Fact(DisplayName = "04.5 - Level 2 - BeEquivalentToShouldFailIfInnerInnerExceptionDataDontMatch")] + public void BeEquivalentToShouldFailIfAggregateInnerInnerExceptionDataDontMatch() + { + // given + string exceptionMessage = GetRandomString(); + string mutualKey = GetRandomString(); + KeyValuePair> expectedDataOne = GenerateKeyValuePair(count: 1); + KeyValuePair> expectedDataTwo = GenerateKeyValuePair(count: 1); + KeyValuePair> actualData = GenerateKeyValuePair(count: 1); + KeyValuePair> expectedDataSameKeyName = GenerateKeyValuePair(count: 1, mutualKey); + KeyValuePair> actualDataSameKeyName = GenerateKeyValuePair(count: 1, mutualKey); + + var expectedInnerInnerException = new Xeption( + message: exceptionMessage); + + expectedInnerInnerException.AddData( + key: expectedDataOne.Key, + values: expectedDataOne.Value.ToArray()); + + expectedInnerInnerException.AddData( + key: expectedDataTwo.Key, + values: expectedDataTwo.Value.ToArray()); + + expectedInnerInnerException.AddData( + key: expectedDataSameKeyName.Key, + values: expectedDataSameKeyName.Value.ToArray()); + + var actualInnerInnerException = new Xeption( + message: exceptionMessage); + + actualInnerInnerException.AddData( + key: actualDataSameKeyName.Key, + values: actualDataSameKeyName.Value.ToArray()); + + actualInnerInnerException.AddData( + key: actualData.Key, + values: actualData.Value.ToArray()); + + Xeption expectedInnerException = new Xeption( + message: exceptionMessage, + innerException: expectedInnerInnerException); + + Xeption actualInnerException = new Xeption( + message: exceptionMessage, + innerException: actualInnerInnerException); + + var expectedError = new StringBuilder(); + expectedError.AppendLine($"Expected inner exception (level 2) to:"); + + expectedError.AppendLine( + $"- have a data count of {expectedInnerInnerException.Data.Count}, " + + $"but found {actualInnerInnerException.Data.Count}"); + + expectedError.AppendLine( + $"- NOT contain key \"{actualData.Key}\""); + + expectedError.AppendLine( + $"- contain key \"{expectedDataOne.Key}\" with value(s) ['{expectedDataOne.Value[0]}']"); + + expectedError.AppendLine( + $"- contain key \"{expectedDataTwo.Key}\" with value(s) ['{expectedDataTwo.Value[0]}']"); + + expectedError.AppendLine( + $"- have key \"{mutualKey}\" with value(s) ['{expectedDataSameKeyName.Value[0]}'], " + + $"but found value(s) ['{actualDataSameKeyName.Value[0]}']"); + + var expectedException = new Xeption( + message: exceptionMessage, + innerException: expectedInnerException); + + var actualException = new Xeption( + message: exceptionMessage, + innerException: actualInnerException); + + // when + Action assertAction = () => + actualException.Should().BeEquivalentTo(expectedException); + + XunitException actualError = + Assert.Throws(assertAction); + + //then + actualError.Message.Should().BeEquivalentTo(expectedError.ToString().Trim()); + } + + [Fact(DisplayName = "05.0 - Aggregate - BeEquivalentToShouldPassIfNullExceptionsMatch")] + public void AggregateBeEquivalentToShouldPassIfNullExceptionsMatch() + { + // given + AggregateException expectedException = null; + AggregateException actualException = null; + + // when then + actualException.Should().BeEquivalentTo(expectedException); + } + + [Fact(DisplayName = "06.1 - Aggregate - BeEquivalentToShouldPassIfExceptionsMatch")] + public void AggregateBeEquivalentToShouldPassIfExceptionsMatch() + { + // given + string randomMessage = GetRandomString(); + var expectedException = new AggregateException(message: randomMessage); + var actualException = new AggregateException(message: randomMessage); + + // when then + actualException.Should().BeEquivalentTo(expectedException); + } + + [Fact(DisplayName = "06.2 - Aggregate - BeEquivalentToShouldPassIfExceptionDataMatch")] + public void AggregateBeEquivalentToShouldPassIfExceptionDataMatch() + { + // given + string exceptionMessage = GetRandomString(); + KeyValuePair> randomData = GenerateKeyValuePair(count: 2); + KeyValuePair> expectedData = randomData.DeepClone(); + KeyValuePair> actualData = randomData.DeepClone(); + + var expectedException = new AggregateException( + message: exceptionMessage); + + expectedException.Data.Add( + key: expectedData.Key, + value: expectedData.Value.ToArray()); + + var actualException = new AggregateException( + message: exceptionMessage); + + actualException.Data.Add( + key: actualData.Key, + value: actualData.Value.ToArray()); + + // when then + actualException.Should().BeEquivalentTo(expectedException); + } + + [Fact(DisplayName = "06.3 - Aggregate - BeEquivalentToShouldFailIfExceptionDataDontMatch")] + public void AggregateBeEquivalentToShouldFailIfExceptionDataDontMatch() + { + // given + string exceptionMessage = GetRandomString(); + string mutualKey = $"mutual-{GetRandomString()}"; + KeyValuePair> expectedDataOne = GenerateKeyValuePair(count: 1); + KeyValuePair> expectedDataTwo = GenerateKeyValuePair(count: 1); + KeyValuePair> actualData = GenerateKeyValuePair(count: 1); + + KeyValuePair> expectedDataSameKeyName = + GenerateKeyValuePair(count: 1, keyName: mutualKey); + + KeyValuePair> actualDataSameKeyName = + GenerateKeyValuePair(count: 1, keyName: mutualKey); + + var expectedException = new AggregateException( + message: exceptionMessage); + + expectedException.Data.Add( + key: expectedDataOne.Key, + value: expectedDataOne.Value.ToArray()); + + expectedException.Data.Add( + key: expectedDataTwo.Key, + value: expectedDataTwo.Value.ToArray()); + + expectedException.Data.Add( + key: expectedDataSameKeyName.Key, + value: expectedDataSameKeyName.Value.ToArray()); + + var actualException = new AggregateException( + message: exceptionMessage); + + actualException.Data.Add( + key: actualDataSameKeyName.Key, + value: actualDataSameKeyName.Value.ToArray()); + + actualException.Data.Add( + key: actualData.Key, + value: actualData.Value.ToArray()); + + var expectedError = new StringBuilder(); + expectedError.AppendLine($"Expected exception to:"); + + expectedError.AppendLine( + $"- have a data count of {expectedException.Data.Count}, " + + $"but found {actualException.Data.Count}"); + + expectedError.AppendLine( + $"- NOT contain key \"{actualData.Key}\""); + + expectedError.AppendLine( + $"- contain key \"{expectedDataOne.Key}\" with value(s) ['{expectedDataOne.Value[0]}']"); + + expectedError.AppendLine( + $"- contain key \"{expectedDataTwo.Key}\" with value(s) ['{expectedDataTwo.Value[0]}']"); + + expectedError.AppendLine( + $"- have key \"{mutualKey}\" with value(s) ['{expectedDataSameKeyName.Value[0]}'], " + + $"but found value(s) ['{actualDataSameKeyName.Value[0]}']"); + + // when + Action assertAction = () => + actualException.Should().BeEquivalentTo(expectedException); + + XunitException actualError = + Assert.Throws(assertAction); + + //then + actualError.Message.Should().BeEquivalentTo(expectedError.ToString().Trim()); + } + + [Fact(DisplayName = "07.1 - Aggregate - BeEquivalentToShouldPassIfInnerExceptionMatch")] + public void AggregateBeEquivalentToShouldPassIfInnerExceptionMatchOnType() { // given string randomMessage = GetRandomString(); Xeption expectedInnerException = new Xeption(message: randomMessage); Xeption actualInnerException = new Xeption(message: randomMessage); - string sameKey = GetRandomString(); - string expectedSameValue = GetRandomString(); - string unexpectedSameValue = GetRandomString(); - string expectedKey = $"expected-{GetRandomString()}"; - string expectedValue = GetRandomString(); - string unexpectedKey = $"unexpected-{GetRandomString()}"; - string unexpectedValue = GetRandomString(); + + var expectedException = new AggregateException( + message: randomMessage, + innerException: expectedInnerException); + + var actualException = new AggregateException( + message: randomMessage, + innerException: actualInnerException); + + // when then + actualException.Should().BeEquivalentTo(expectedException); + } + + [Fact(DisplayName = "07.2 - Aggregate - Level 0 - BeEquivalentToShouldFailIfInnerExceptionsTypeDontMatch")] + public void AggregateBeEquivalentToShouldFailIfInnerExceptionsTypeDontMatch() + { + // given + string randomMessage = GetRandomString(); + Xeption expectedInnerException = new Xeption(message: randomMessage); + Exception actualInnerException = new Exception(message: randomMessage); + + var expectedException = new AggregateException( + message: randomMessage, + innerExceptions: expectedInnerException); + + var actualException = new AggregateException( + message: randomMessage, + innerExceptions: actualInnerException); + + string expectedMessage = + $"* Difference in inner exception at index[0] - Expected exception " + + $"to be \"{expectedInnerException.GetType().FullName}\", " + + $"but found \"{actualInnerException.GetType().FullName}\""; + + // when + Action assertAction = () => + actualException.Should().BeEquivalentTo(expectedException); + + XunitException actualError = + Assert.Throws(assertAction); + + //then + actualError.Message.Should().Contain(expectedMessage); + } + + [Fact(DisplayName = "07.3 - Aggregate - Level 0 - BeEquivalentToShouldFailIfInnerExceptionsMessageDontMatch")] + public void AggregateBeEquivalentToShouldFailIfInnerExceptionsMessageDontMatch() + { + // given + string randomMessage = GetRandomString(); + var expectedInnerException = new Xeption(message: GetRandomString()); + var actualInnerException = new Xeption(message: GetRandomString()); + + var expectedException = new AggregateException( + message: randomMessage, + innerExceptions: expectedInnerException); + + var actualException = new AggregateException( + message: randomMessage, + innerExceptions: actualInnerException); + + string expectedMessage = + $"* Difference in inner exception at index[0] - Expected exception message to be \"{expectedInnerException.Message}\", " + + $"but found \"{actualInnerException.Message}\""; + + // when + Action assertAction = () => + actualException.Should().BeEquivalentTo(expectedException); + + XunitException actualError = + Assert.Throws(assertAction); + + //then + actualError.Message.Should().Contain(expectedMessage); + } + + [Fact(DisplayName = "07.4 - Aggregate - Level 0 - BeEquivalentToShouldPassIfInnerExceptionDataMatch")] + public void AggregateBeEquivalentToShouldPassIfInnerExceptionsDataMatch() + { + // given + string exceptionMessage = GetRandomString(); + KeyValuePair> randomData = GenerateKeyValuePair(count: 1); + KeyValuePair> expectedData = randomData.DeepClone(); + KeyValuePair> actualData = randomData.DeepClone(); + + var expectedInnerException = new Xeption( + message: exceptionMessage); + + expectedInnerException.AddData( + key: expectedData.Key, + values: expectedData.Value.ToArray()); + + var actualInnerException = new Xeption( + message: exceptionMessage); + + actualInnerException.AddData( + key: actualData.Key, + values: actualData.Value.ToArray()); + + var expectedException = new AggregateException( + message: exceptionMessage, + innerExceptions: expectedInnerException); + + var actualException = new AggregateException( + message: exceptionMessage, + innerExceptions: actualInnerException); + + // when then + actualException.Should().BeEquivalentTo(expectedException); + } + + [Fact(DisplayName = "07.5 - Aggregate - Level 0 - BeEquivalentToShouldFailIfInnerExceptionDataDontMatch")] + public void AggregateBeEquivalentToShouldFailIfInnerExceptionsDataDontMatch() + { + // given + string exceptionMessage = GetRandomString(); + string mutualKey = GetRandomString(); + KeyValuePair> expectedDataOne = GenerateKeyValuePair(count: 1); + KeyValuePair> expectedDataTwo = GenerateKeyValuePair(count: 1); + KeyValuePair> actualData = GenerateKeyValuePair(count: 1); + KeyValuePair> expectedDataSameKeyName = GenerateKeyValuePair(count: 1, mutualKey); + KeyValuePair> actualDataSameKeyName = GenerateKeyValuePair(count: 1, mutualKey); + + var expectedInnerException = new Xeption( + message: exceptionMessage); + + expectedInnerException.AddData( + key: expectedDataOne.Key, + values: expectedDataOne.Value.ToArray()); expectedInnerException.AddData( - key: expectedKey, - values: new[] { expectedValue }); + key: expectedDataTwo.Key, + values: expectedDataTwo.Value.ToArray()); expectedInnerException.AddData( - key: sameKey, - values: new[] { expectedSameValue }); + key: expectedDataSameKeyName.Key, + values: expectedDataSameKeyName.Value.ToArray()); + + var actualInnerException = new Xeption( + message: exceptionMessage); actualInnerException.AddData( - key: unexpectedKey, - values: new[] { unexpectedValue }); + key: actualDataSameKeyName.Key, + values: actualDataSameKeyName.Value.ToArray()); actualInnerException.AddData( - key: sameKey, - values: new[] { unexpectedSameValue }); + key: actualData.Key, + values: actualData.Value.ToArray()); + + var expectedError = new StringBuilder(); + expectedError.AppendLine($"Aggregate exception differences:"); + expectedError.AppendLine($"* Difference in inner exception at index[0] - Expected exception to:"); + + expectedError.AppendLine( + $"- have a data count of {expectedInnerException.Data.Count}, " + + $"but found {actualInnerException.Data.Count}"); + + expectedError.AppendLine( + $"- NOT contain key \"{actualData.Key}\""); + + expectedError.AppendLine( + $"- contain key \"{expectedDataOne.Key}\" with value(s) ['{expectedDataOne.Value[0]}']"); + + expectedError.AppendLine( + $"- contain key \"{expectedDataTwo.Key}\" with value(s) ['{expectedDataTwo.Value[0]}']"); - AggregateException expectedAggregateException = new AggregateException( + expectedError.AppendLine( + $"- have key \"{mutualKey}\" with value(s) ['{expectedDataSameKeyName.Value[0]}'], " + + $"but found value(s) ['{actualDataSameKeyName.Value[0]}']"); + + var expectedException = new AggregateException( + message: exceptionMessage, + innerExceptions: expectedInnerException); + + var actualException = new AggregateException( + message: exceptionMessage, + innerExceptions: actualInnerException); + + // when + Action assertAction = () => + actualException.Should().BeEquivalentTo(expectedException); + + XunitException actualError = + Assert.Throws(assertAction); + + //then + actualError.Message.Should().BeEquivalentTo(expectedError.ToString().Trim()); + } + + [Fact(DisplayName = "08.1 - Aggregate - Level 1 - BeEquivalentToShouldPassIfInnerInnerExceptionsMatch")] + public void AggregateBeEquivalentToShouldPassIfInnerInnerExceptionsMatch() + { + // given + string randomMessage = GetRandomString(); + Xeption expectedInnerInnerException = new Xeption(message: randomMessage); + Xeption actualInnerInnerException = new Xeption(message: randomMessage); + + Xeption expectedInnerException = new Xeption( message: randomMessage, - innerExceptions: new List { expectedInnerException }); + innerException: expectedInnerInnerException); - AggregateException actualAggregateException = new AggregateException( + Xeption actualInnerException = new Xeption( message: randomMessage, - innerExceptions: new List { actualInnerException }); + innerException: actualInnerInnerException); - var expectedException = new Xeption( + var expectedException = new AggregateException( message: randomMessage, - innerException: expectedAggregateException); + innerExceptions: expectedInnerException); - var actualException = new Xeption( + var actualException = new AggregateException( message: randomMessage, - innerException: actualAggregateException); + innerExceptions: actualInnerException); - string expectedMessage = - $"Expected aggregate inner exception [0] to:{Environment.NewLine}" + - $"- contain key '{expectedKey}' with value(s) [{expectedValue}].{Environment.NewLine}" + - $"- NOT contain key '{unexpectedKey}'.{Environment.NewLine}" + - $"- have key '{sameKey}' with value(s) [{expectedSameValue}], " + - $"but found value(s) [{unexpectedSameValue}]."; + // when then + actualException.Should().BeEquivalentTo(expectedException); + } + + [Fact(DisplayName = "08.2 - Aggregate - Level 1 - BeEquivalentToShouldFailIfInnerInnerExceptionsTypeDontMatch")] + public void AggregateBeEquivalentToShouldFailIfInnerInnerExceptionsTypeDontMatch() + { + // given + string randomMessage = GetRandomString(); + Xeption expectedInnerInnerException = new Xeption(message: randomMessage); + Exception actualInnerInnerException = new Exception(message: randomMessage); + + Xeption expectedInnerException = new Xeption( + message: randomMessage, + innerException: expectedInnerInnerException); + + Xeption actualInnerException = new Xeption( + message: randomMessage, + innerException: actualInnerInnerException); + + var expectedException = new AggregateException( + message: randomMessage, + innerExceptions: expectedInnerException); + + var actualException = new AggregateException( + message: randomMessage, + innerExceptions: actualInnerException); + + var expectedError = new StringBuilder(); + expectedError.AppendLine($"Aggregate exception differences:"); + expectedError.AppendLine($"* Difference in inner exception at index[0] - " + + $"Expected inner exception (level 1) to be \"{expectedInnerInnerException.GetType().FullName}\", " + + $"but found \"{actualInnerInnerException.GetType().FullName}\""); // when Action assertAction = () => @@ -544,7 +995,180 @@ public void BeEquivalentToShouldFailIfAggregateInnerExceptionsDataDontMatch() Assert.Throws(assertAction); //then - actualError.Message.Should().Contain(expectedMessage); + actualError.Message.Should().Contain(expectedError.ToString().Trim()); + } + + [Fact(DisplayName = "08.3 - Aggregate - Level 1 - BeEquivalentToShouldFailIfInnerInnerExceptionMessageDontMatch")] + public void AggregateBeEquivalentToShouldFailIfInnerInnerExceptionsMessageDontMatch() + { + // given + string randomMessage = GetRandomString(); + var expectedInnerInnerException = new Xeption(message: GetRandomString()); + var actualInnerInnerException = new Xeption(message: GetRandomString()); + + Xeption expectedInnerException = new Xeption( + message: randomMessage, + innerException: expectedInnerInnerException); + + Xeption actualInnerException = new Xeption( + message: randomMessage, + innerException: actualInnerInnerException); + + var expectedException = new AggregateException( + message: randomMessage, + innerExceptions: expectedInnerException); + + var actualException = new AggregateException( + message: randomMessage, + innerExceptions: actualInnerException); + + var expectedError = new StringBuilder(); + expectedError.AppendLine($"Aggregate exception differences:"); + expectedError.AppendLine($"* Difference in inner exception at index[0] - " + + $"Expected inner exception (level 1) message to be \"{expectedInnerInnerException.Message}\", " + + $"but found \"{actualInnerInnerException.Message}\""); + + // when + Action assertAction = () => + actualException.Should().BeEquivalentTo(expectedException); + + XunitException actualError = + Assert.Throws(assertAction); + + //then + actualError.Message.Should().Contain(expectedError.ToString().Trim()); + } + + [Fact(DisplayName = "08.4 - Aggregate - Level 1 - BeEquivalentToShouldPassIfInnerInnerExceptionDataMatch")] + public void AggregateBeEquivalentToShouldPassIfInnerInnerExceptionsDataMatch() + { + // given + string exceptionMessage = GetRandomString(); + KeyValuePair> randomData = GenerateKeyValuePair(count: 1); + KeyValuePair> expectedData = randomData.DeepClone(); + KeyValuePair> actualData = randomData.DeepClone(); + + var expectedInnerInnerException = new Xeption( + message: exceptionMessage); + + expectedInnerInnerException.AddData( + key: expectedData.Key, + values: expectedData.Value.ToArray()); + + var actualInnerInnerException = new Xeption( + message: exceptionMessage); + + actualInnerInnerException.AddData( + key: actualData.Key, + values: actualData.Value.ToArray()); + + Xeption expectedInnerException = new Xeption( + message: exceptionMessage, + innerException: expectedInnerInnerException); + + Xeption actualInnerException = new Xeption( + message: exceptionMessage, + innerException: actualInnerInnerException); + + var expectedException = new AggregateException( + message: exceptionMessage, + innerExceptions: expectedInnerException); + + var actualException = new AggregateException( + message: exceptionMessage, + innerExceptions: actualInnerException); + + // when then + actualException.Should().BeEquivalentTo(expectedException); + } + + [Fact(DisplayName = "08.5 - Aggregate - Level 1 - BeEquivalentToShouldFailIfInnerInnerExceptionDataDontMatch")] + public void AggregateBeEquivalentToShouldFailIfAggregateInnerInnerExceptionDataDontMatch() + { + // given + string exceptionMessage = GetRandomString(); + string mutualKey = GetRandomString(); + KeyValuePair> expectedDataOne = GenerateKeyValuePair(count: 1); + KeyValuePair> expectedDataTwo = GenerateKeyValuePair(count: 1); + KeyValuePair> actualData = GenerateKeyValuePair(count: 1); + KeyValuePair> expectedDataSameKeyName = GenerateKeyValuePair(count: 1, mutualKey); + KeyValuePair> actualDataSameKeyName = GenerateKeyValuePair(count: 1, mutualKey); + + var expectedInnerInnerException = new Xeption( + message: exceptionMessage); + + expectedInnerInnerException.AddData( + key: expectedDataOne.Key, + values: expectedDataOne.Value.ToArray()); + + expectedInnerInnerException.AddData( + key: expectedDataTwo.Key, + values: expectedDataTwo.Value.ToArray()); + + expectedInnerInnerException.AddData( + key: expectedDataSameKeyName.Key, + values: expectedDataSameKeyName.Value.ToArray()); + + var actualInnerInnerException = new Xeption( + message: exceptionMessage); + + actualInnerInnerException.AddData( + key: actualDataSameKeyName.Key, + values: actualDataSameKeyName.Value.ToArray()); + + actualInnerInnerException.AddData( + key: actualData.Key, + values: actualData.Value.ToArray()); + + Xeption expectedInnerException = new Xeption( + message: exceptionMessage, + innerException: expectedInnerInnerException); + + Xeption actualInnerException = new Xeption( + message: exceptionMessage, + innerException: actualInnerInnerException); + + var expectedError = new StringBuilder(); + expectedError.AppendLine($"Aggregate exception differences:"); + + expectedError.AppendLine( + $"* Difference in inner exception at index[0] - " + + $"Expected inner exception (level 1) to:"); + + expectedError.AppendLine( + $"- have a data count of {expectedInnerInnerException.Data.Count}, " + + $"but found {actualInnerInnerException.Data.Count}"); + + expectedError.AppendLine( + $"- NOT contain key \"{actualData.Key}\""); + + expectedError.AppendLine( + $"- contain key \"{expectedDataOne.Key}\" with value(s) ['{expectedDataOne.Value[0]}']"); + + expectedError.AppendLine( + $"- contain key \"{expectedDataTwo.Key}\" with value(s) ['{expectedDataTwo.Value[0]}']"); + + expectedError.AppendLine( + $"- have key \"{mutualKey}\" with value(s) ['{expectedDataSameKeyName.Value[0]}'], " + + $"but found value(s) ['{actualDataSameKeyName.Value[0]}']"); + + var expectedException = new AggregateException( + message: exceptionMessage, + innerExceptions: expectedInnerException); + + var actualException = new AggregateException( + message: exceptionMessage, + innerExceptions: actualInnerException); + + // when + Action assertAction = () => + actualException.Should().BeEquivalentTo(expectedException); + + XunitException actualError = + Assert.Throws(assertAction); + + //then + actualError.Message.Should().BeEquivalentTo(expectedError.ToString().Trim()); } } } diff --git a/Xeption.Tests/XeptionAssertionTests.cs b/Xeption.Tests/XeptionAssertionTests.cs index d272464..cfdc1d1 100644 --- a/Xeption.Tests/XeptionAssertionTests.cs +++ b/Xeption.Tests/XeptionAssertionTests.cs @@ -34,11 +34,5 @@ private static KeyValuePair> GenerateKeyValuePair(int count return keyValuePair; } - - internal class OtherTarget - { - public static void ThrowingExceptionMethod() => - throw new Exception(); - } } } diff --git a/Xeption.Tests/XeptionExtensionTests.Logic.SameExceptionAs.cs b/Xeption.Tests/XeptionExtensionTests.Logic.SameExceptionAs.cs index 7ac36a4..7397a5d 100644 --- a/Xeption.Tests/XeptionExtensionTests.Logic.SameExceptionAs.cs +++ b/Xeption.Tests/XeptionExtensionTests.Logic.SameExceptionAs.cs @@ -3,6 +3,8 @@ // ---------------------------------------------------------------------------------- using System; +using System.Collections.Generic; +using System.Text; using FluentAssertions; using Force.DeepCloner; using Xunit; @@ -673,5 +675,1176 @@ public void ShouldReturnFalseIfExceptionDataDontMatchWithErrorDetails() Assert.False(actualComparisonResult); message.Should().NotBeNullOrWhiteSpace(); } + + [Fact(DisplayName = "01.0 - Level 0 - SameExceptionAsShouldPassIfNullExceptionsMatch")] + public void SameExceptionAsShouldPassIfNullExceptionsMatch() + { + // given + Xeption expectedException = null; + Xeption actualException = null; + + // when + bool result = actualException.SameExceptionAs(expectedException, out string actualMessage); + + // then + Assert.True(result); + Assert.True(String.IsNullOrWhiteSpace(actualMessage)); + } + + [Fact(DisplayName = "02.1 - Level 0 - SameExceptionAsShouldPassIfExceptionsMatch")] + public void SameExceptionAsShouldPassIfExceptionsMatch() + { + // given + string randomMessage = GetRandomString(); + var expectedException = new Xeption(message: randomMessage); + var actualException = new Xeption(message: randomMessage); + + // when + bool result = actualException.SameExceptionAs(expectedException, out string actualMessage); + + // then + Assert.True(result); + Assert.True(String.IsNullOrWhiteSpace(actualMessage)); + } + + [Fact(DisplayName = "02.2 - Level 0 - SameExceptionAsShouldFailIfExceptionsDontMatchOnType")] + public void SameExceptionAsShouldFailIfExceptionsDontMatchOnType() + { + // given + string randomMessage = GetRandomString(); + var expectedException = new Xeption(message: randomMessage); + var actualException = new Exception(message: randomMessage); + + string expectedMessage = + $"Expected exception to be \"{expectedException.GetType().FullName}\", " + + $"but found \"{actualException.GetType().FullName}\""; + + // when + bool result = actualException.SameExceptionAs(expectedException, out string actualMessage); + + //then + Assert.False(result); + actualMessage.Should().BeEquivalentTo(expectedMessage); + } + + [Fact(DisplayName = "02.3 - Level 0 - SameExceptionAsShouldFailIfExceptionMessagesDontMatch")] + public void SameExceptionAsShouldFailIfExceptionMessagesDontMatch() + { + // given + var expectedException = new Xeption(message: GetRandomString()); + var actualException = new Xeption(message: GetRandomString()); + + string expectedMessage = + $"Expected exception message to be \"{expectedException.Message}\", " + + $"but found \"{actualException.Message}\""; + + // when + bool result = actualException.SameExceptionAs(expectedException, out string actualMessage); + + //then + Assert.False(result); + actualMessage.Should().BeEquivalentTo(expectedMessage); + } + + [Fact(DisplayName = "02.4 - Level 0 - SameExceptionAsShouldPassIfExceptionDataMatch")] + public void SameExceptionAsShouldPassIfExceptionDataMatch() + { + // given + string exceptionMessage = GetRandomString(); + KeyValuePair> randomData = GenerateKeyValuePair(count: 1); + KeyValuePair> expectedData = randomData.DeepClone(); + KeyValuePair> actualData = randomData.DeepClone(); + + var expectedException = new Xeption( + message: exceptionMessage); + + expectedException.AddData( + key: expectedData.Key, + values: expectedData.Value.ToArray()); + + var actualException = new Xeption( + message: exceptionMessage); + + actualException.AddData( + key: actualData.Key, + values: actualData.Value.ToArray()); + + // when + bool result = actualException.SameExceptionAs(expectedException, out string actualMessage); + + // then + Assert.True(result); + Assert.True(String.IsNullOrWhiteSpace(actualMessage)); + } + + [Fact(DisplayName = "02.5 - Level 0 - SameExceptionAsShouldFailIfExceptionDataDontMatch")] + public void SameExceptionAsShouldFailIfExceptionDataDontMatch() + { + // given + string exceptionMessage = GetRandomString(); + string mutualKey = $"mutual-{GetRandomString()}"; + KeyValuePair> expectedDataOne = GenerateKeyValuePair(count: 1); + KeyValuePair> expectedDataTwo = GenerateKeyValuePair(count: 1); + KeyValuePair> actualData = GenerateKeyValuePair(count: 1); + + KeyValuePair> expectedDataSameKeyName = + GenerateKeyValuePair(count: 1, keyName: mutualKey); + + KeyValuePair> actualDataSameKeyName = + GenerateKeyValuePair(count: 1, keyName: mutualKey); + + var expectedException = new Xeption( + message: exceptionMessage); + + expectedException.AddData( + key: expectedDataOne.Key, + values: expectedDataOne.Value.ToArray()); + + expectedException.AddData( + key: expectedDataTwo.Key, + values: expectedDataTwo.Value.ToArray()); + + expectedException.AddData( + key: expectedDataSameKeyName.Key, + values: expectedDataSameKeyName.Value.ToArray()); + + var actualException = new Xeption( + message: exceptionMessage); + + actualException.AddData( + key: actualDataSameKeyName.Key, + values: actualDataSameKeyName.Value.ToArray()); + + actualException.AddData( + key: actualData.Key, + values: actualData.Value.ToArray()); + + var expectedMessage = new StringBuilder(); + expectedMessage.AppendLine($"Expected exception to:"); + + expectedMessage.AppendLine( + $"- have a data count of {expectedException.Data.Count}, " + + $"but found {actualException.Data.Count}"); + + expectedMessage.AppendLine( + $"- NOT contain key \"{actualData.Key}\""); + + expectedMessage.AppendLine( + $"- contain key \"{expectedDataOne.Key}\" with value(s) ['{expectedDataOne.Value[0]}']"); + + expectedMessage.AppendLine( + $"- contain key \"{expectedDataTwo.Key}\" with value(s) ['{expectedDataTwo.Value[0]}']"); + + expectedMessage.AppendLine( + $"- have key \"{mutualKey}\" with value(s) ['{expectedDataSameKeyName.Value[0]}'], " + + $"but found value(s) ['{actualDataSameKeyName.Value[0]}']"); + + // when + bool result = actualException.SameExceptionAs(expectedException, out string actualMessage); + + //then + Assert.False(result); + actualMessage.Should().BeEquivalentTo(expectedMessage.ToString().Trim()); + } + + [Fact(DisplayName = "03.1 - Level 0 - SameExceptionAsShouldPassIfInnerExceptionsMatch")] + public void SameExceptionAsShouldPassIfInnerExceptionsMatchOnType() + { + // given + string randomMessage = GetRandomString(); + Xeption expectedInnerException = new Xeption(message: randomMessage); + Xeption actualInnerException = new Xeption(message: randomMessage); + + var expectedException = new Xeption( + message: randomMessage, + innerException: expectedInnerException); + + var actualException = new Xeption( + message: randomMessage, + innerException: actualInnerException); + + // when + bool result = actualException.SameExceptionAs(expectedException, out string actualMessage); + + // then + Assert.True(result); + Assert.True(String.IsNullOrWhiteSpace(actualMessage)); + } + + [Fact(DisplayName = "03.2 - Level 1 - SameExceptionAsShouldFailIfInnerExceptionsTypeDontMatch")] + public void SameExceptionAsShouldFailIfInnerExceptionsTypeDontMatch() + { + // given + string randomMessage = GetRandomString(); + Xeption expectedInnerException = new Xeption(message: randomMessage); + Exception actualInnerException = new Exception(message: randomMessage); + + var expectedException = new Xeption( + message: randomMessage, + innerException: expectedInnerException); + + var actualException = new Xeption( + message: randomMessage, + innerException: actualInnerException); + + string expectedMessage = + $"Expected inner exception (level 1) to be \"{expectedInnerException.GetType().FullName}\", " + + $"but found \"{actualInnerException.GetType().FullName}\""; + + // when + bool result = actualException.SameExceptionAs(expectedException, out string actualMessage); + + //then + Assert.False(result); + actualMessage.Should().BeEquivalentTo(expectedMessage.ToString().Trim()); + } + + [Fact(DisplayName = "03.3 - Level 1 - SameExceptionAsShouldFailIfInnerExceptionMessageDontMatch")] + public void SameExceptionAsShouldFailIfInnerExceptionMessageDontMatch() + { + // given + string randomMessage = GetRandomString(); + var expectedInnerException = new Xeption(message: GetRandomString()); + var actualInnerException = new Xeption(message: GetRandomString()); + + var expectedException = new Xeption( + message: randomMessage, + innerException: expectedInnerException); + + var actualException = new Xeption( + message: randomMessage, + innerException: actualInnerException); + + string expectedMessage = + $"Expected inner exception (level 1) message to be \"{expectedInnerException.Message}\", " + + $"but found \"{actualInnerException.Message}\""; + + // when + bool result = actualException.SameExceptionAs(expectedException, out string actualMessage); + + //then + Assert.False(result); + actualMessage.Should().BeEquivalentTo(expectedMessage.ToString().Trim()); + } + + [Fact(DisplayName = "03.4 - Level 1 - SameExceptionAsShouldPassIfInnerExceptionDataMatch")] + public void SameExceptionAsShouldPassIfInnerExceptionDataMatch() + { + // given + string exceptionMessage = GetRandomString(); + KeyValuePair> randomData = GenerateKeyValuePair(count: 1); + KeyValuePair> expectedData = randomData.DeepClone(); + KeyValuePair> actualData = randomData.DeepClone(); + + var expectedInnerException = new Xeption( + message: exceptionMessage); + + expectedInnerException.AddData( + key: expectedData.Key, + values: expectedData.Value.ToArray()); + + var actualInnerException = new Xeption( + message: exceptionMessage); + + actualInnerException.AddData( + key: actualData.Key, + values: actualData.Value.ToArray()); + + var expectedException = new Xeption( + message: exceptionMessage, + innerException: expectedInnerException); + + var actualException = new Xeption( + message: exceptionMessage, + innerException: actualInnerException); + + // when + bool result = actualException.SameExceptionAs(expectedException, out string actualMessage); + + // then + Assert.True(result); + Assert.True(String.IsNullOrWhiteSpace(actualMessage)); + } + + [Fact(DisplayName = "03.5 - Level 1 - SameExceptionAsShouldFailIfInnerExceptionDataDontMatch")] + public void SameExceptionAsShouldFailIfInnerExceptionDataDontMatch() + { + // given + string exceptionMessage = GetRandomString(); + string mutualKey = GetRandomString(); + KeyValuePair> expectedDataOne = GenerateKeyValuePair(count: 1); + KeyValuePair> expectedDataTwo = GenerateKeyValuePair(count: 1); + KeyValuePair> actualData = GenerateKeyValuePair(count: 1); + KeyValuePair> expectedDataSameKeyName = GenerateKeyValuePair(count: 1, mutualKey); + KeyValuePair> actualDataSameKeyName = GenerateKeyValuePair(count: 1, mutualKey); + + var expectedInnerException = new Xeption( + message: exceptionMessage); + + expectedInnerException.AddData( + key: expectedDataOne.Key, + values: expectedDataOne.Value.ToArray()); + + expectedInnerException.AddData( + key: expectedDataTwo.Key, + values: expectedDataTwo.Value.ToArray()); + + expectedInnerException.AddData( + key: expectedDataSameKeyName.Key, + values: expectedDataSameKeyName.Value.ToArray()); + + var actualInnerException = new Xeption( + message: exceptionMessage); + + actualInnerException.AddData( + key: actualDataSameKeyName.Key, + values: actualDataSameKeyName.Value.ToArray()); + + actualInnerException.AddData( + key: actualData.Key, + values: actualData.Value.ToArray()); + + var expectedMessage = new StringBuilder(); + expectedMessage.AppendLine($"Expected inner exception (level 1) to:"); + + expectedMessage.AppendLine( + $"- have a data count of {expectedInnerException.Data.Count}, " + + $"but found {actualInnerException.Data.Count}"); + + expectedMessage.AppendLine( + $"- NOT contain key \"{actualData.Key}\""); + + expectedMessage.AppendLine( + $"- contain key \"{expectedDataOne.Key}\" with value(s) ['{expectedDataOne.Value[0]}']"); + + expectedMessage.AppendLine( + $"- contain key \"{expectedDataTwo.Key}\" with value(s) ['{expectedDataTwo.Value[0]}']"); + + expectedMessage.AppendLine( + $"- have key \"{mutualKey}\" with value(s) ['{expectedDataSameKeyName.Value[0]}'], " + + $"but found value(s) ['{actualDataSameKeyName.Value[0]}']"); + + var expectedException = new Xeption( + message: exceptionMessage, + innerException: expectedInnerException); + + var actualException = new Xeption( + message: exceptionMessage, + innerException: actualInnerException); + + // when + bool result = actualException.SameExceptionAs(expectedException, out string actualMessage); + + //then + Assert.False(result); + actualMessage.Should().BeEquivalentTo(expectedMessage.ToString().Trim()); + } + + [Fact(DisplayName = "04.1 - Level 2 - SameExceptionAsShouldPassIfInnerInnerExceptionsMatch")] + public void SameExceptionAsShouldPassIfInnerInnerExceptionsMatch() + { + // given + string randomMessage = GetRandomString(); + Xeption expectedInnerInnerException = new Xeption(message: randomMessage); + Xeption actualInnerInnerException = new Xeption(message: randomMessage); + + Xeption expectedInnerException = new Xeption( + message: randomMessage, + innerException: expectedInnerInnerException); + + Xeption actualInnerException = new Xeption( + message: randomMessage, + innerException: actualInnerInnerException); + + var expectedException = new Xeption( + message: randomMessage, + innerException: expectedInnerException); + + var actualException = new Xeption( + message: randomMessage, + innerException: actualInnerException); + + // when + bool result = actualException.SameExceptionAs(expectedException, out string actualMessage); + + // then + Assert.True(result); + Assert.True(String.IsNullOrWhiteSpace(actualMessage)); + } + + [Fact(DisplayName = "04.2 - Level 2 - SameExceptionAsShouldFailIfInnerInnerExceptionsTypeDontMatch")] + public void SameExceptionAsShouldFailIfInnerInnerExceptionsTypeDontMatch() + { + // given + string randomMessage = GetRandomString(); + Xeption expectedInnerInnerException = new Xeption(message: randomMessage); + Exception actualInnerInnerException = new Exception(message: randomMessage); + + Xeption expectedInnerException = new Xeption( + message: randomMessage, + innerException: expectedInnerInnerException); + + Xeption actualInnerException = new Xeption( + message: randomMessage, + innerException: actualInnerInnerException); + + var expectedException = new Xeption( + message: randomMessage, + innerException: expectedInnerException); + + var actualException = new Xeption( + message: randomMessage, + innerException: actualInnerException); + + string expectedMessage = + $"Expected inner exception (level 2) to be \"{expectedInnerInnerException.GetType().FullName}\", " + + $"but found \"{actualInnerInnerException.GetType().FullName}\""; + + // when + bool result = actualException.SameExceptionAs(expectedException, out string actualMessage); + + //then + Assert.False(result); + actualMessage.Should().BeEquivalentTo(expectedMessage.ToString().Trim()); + } + + [Fact(DisplayName = "04.3 - Level 2 - SameExceptionAsShouldFailIfInnerInnerExceptionMessageDontMatch")] + public void SameExceptionAsShouldFailIfInnerInnerExceptionMessageDontMatch() + { + // given + string randomMessage = GetRandomString(); + var expectedInnerInnerException = new Xeption(message: GetRandomString()); + var actualInnerInnerException = new Xeption(message: GetRandomString()); + + Xeption expectedInnerException = new Xeption( + message: randomMessage, + innerException: expectedInnerInnerException); + + Xeption actualInnerException = new Xeption( + message: randomMessage, + innerException: actualInnerInnerException); + + var expectedException = new Xeption( + message: randomMessage, + innerException: expectedInnerException); + + var actualException = new Xeption( + message: randomMessage, + innerException: actualInnerException); + + string expectedMessage = + $"Expected inner exception (level 2) message to be \"{expectedInnerInnerException.Message}\", " + + $"but found \"{actualInnerInnerException.Message}\""; + + // when + bool result = actualException.SameExceptionAs(expectedException, out string actualMessage); + + //then + Assert.False(result); + actualMessage.Should().BeEquivalentTo(expectedMessage.ToString().Trim()); + } + + [Fact(DisplayName = "04.4 - Level 2 - SameExceptionAsShouldPassIfInnerInnerExceptionDataMatch")] + public void SameExceptionAsShouldPassIfInnerInnerExceptionDataMatch() + { + // given + string exceptionMessage = GetRandomString(); + KeyValuePair> randomData = GenerateKeyValuePair(count: 1); + KeyValuePair> expectedData = randomData.DeepClone(); + KeyValuePair> actualData = randomData.DeepClone(); + + var expectedInnerInnerException = new Xeption( + message: exceptionMessage); + + expectedInnerInnerException.AddData( + key: expectedData.Key, + values: expectedData.Value.ToArray()); + + var actualInnerInnerException = new Xeption( + message: exceptionMessage); + + actualInnerInnerException.AddData( + key: actualData.Key, + values: actualData.Value.ToArray()); + + Xeption expectedInnerException = new Xeption( + message: exceptionMessage, + innerException: expectedInnerInnerException); + + Xeption actualInnerException = new Xeption( + message: exceptionMessage, + innerException: actualInnerInnerException); + + var expectedException = new Xeption( + message: exceptionMessage, + innerException: expectedInnerException); + + var actualException = new Xeption( + message: exceptionMessage, + innerException: actualInnerException); + + // when + bool result = actualException.SameExceptionAs(expectedException, out string actualMessage); + + // then + Assert.True(result); + Assert.True(String.IsNullOrWhiteSpace(actualMessage)); + } + + [Fact(DisplayName = "04.5 - Level 2 - SameExceptionAsShouldFailIfInnerInnerExceptionDataDontMatch")] + public void SameExceptionAsShouldFailIfAggregateInnerInnerExceptionDataDontMatch() + { + // given + string exceptionMessage = GetRandomString(); + string mutualKey = GetRandomString(); + KeyValuePair> expectedDataOne = GenerateKeyValuePair(count: 1); + KeyValuePair> expectedDataTwo = GenerateKeyValuePair(count: 1); + KeyValuePair> actualData = GenerateKeyValuePair(count: 1); + KeyValuePair> expectedDataSameKeyName = GenerateKeyValuePair(count: 1, mutualKey); + KeyValuePair> actualDataSameKeyName = GenerateKeyValuePair(count: 1, mutualKey); + + var expectedInnerInnerException = new Xeption( + message: exceptionMessage); + + expectedInnerInnerException.AddData( + key: expectedDataOne.Key, + values: expectedDataOne.Value.ToArray()); + + expectedInnerInnerException.AddData( + key: expectedDataTwo.Key, + values: expectedDataTwo.Value.ToArray()); + + expectedInnerInnerException.AddData( + key: expectedDataSameKeyName.Key, + values: expectedDataSameKeyName.Value.ToArray()); + + var actualInnerInnerException = new Xeption( + message: exceptionMessage); + + actualInnerInnerException.AddData( + key: actualDataSameKeyName.Key, + values: actualDataSameKeyName.Value.ToArray()); + + actualInnerInnerException.AddData( + key: actualData.Key, + values: actualData.Value.ToArray()); + + Xeption expectedInnerException = new Xeption( + message: exceptionMessage, + innerException: expectedInnerInnerException); + + Xeption actualInnerException = new Xeption( + message: exceptionMessage, + innerException: actualInnerInnerException); + + var expectedMessage = new StringBuilder(); + expectedMessage.AppendLine($"Expected inner exception (level 2) to:"); + + expectedMessage.AppendLine( + $"- have a data count of {expectedInnerInnerException.Data.Count}, " + + $"but found {actualInnerInnerException.Data.Count}"); + + expectedMessage.AppendLine( + $"- NOT contain key \"{actualData.Key}\""); + + expectedMessage.AppendLine( + $"- contain key \"{expectedDataOne.Key}\" with value(s) ['{expectedDataOne.Value[0]}']"); + + expectedMessage.AppendLine( + $"- contain key \"{expectedDataTwo.Key}\" with value(s) ['{expectedDataTwo.Value[0]}']"); + + expectedMessage.AppendLine( + $"- have key \"{mutualKey}\" with value(s) ['{expectedDataSameKeyName.Value[0]}'], " + + $"but found value(s) ['{actualDataSameKeyName.Value[0]}']"); + + var expectedException = new Xeption( + message: exceptionMessage, + innerException: expectedInnerException); + + var actualException = new Xeption( + message: exceptionMessage, + innerException: actualInnerException); + + // when + bool result = actualException.SameExceptionAs(expectedException, out string actualMessage); + + //then + Assert.False(result); + actualMessage.Should().BeEquivalentTo(expectedMessage.ToString().Trim()); + } + + [Fact(DisplayName = "05.0 - Aggregate - SameExceptionAsShouldPassIfNullExceptionsMatch")] + public void AggregateSameExceptionAsShouldPassIfNullExceptionsMatch() + { + // given + AggregateException expectedException = null; + AggregateException actualException = null; + + // when + bool result = actualException.SameExceptionAs(expectedException, out string actualMessage); + + // then + Assert.True(result); + Assert.True(String.IsNullOrWhiteSpace(actualMessage)); + } + + [Fact(DisplayName = "06.1 - Aggregate - SameExceptionAsShouldPassIfExceptionsMatch")] + public void AggregateSameExceptionAsShouldPassIfExceptionsMatch() + { + // given + string randomMessage = GetRandomString(); + var expectedException = new AggregateException(message: randomMessage); + var actualException = new AggregateException(message: randomMessage); + + // when + bool result = actualException.SameExceptionAs(expectedException, out string actualMessage); + + // then + Assert.True(result); + Assert.True(String.IsNullOrWhiteSpace(actualMessage)); + } + + [Fact(DisplayName = "06.2 - Aggregate - SameExceptionAsShouldPassIfExceptionDataMatch")] + public void AggregateSameExceptionAsShouldPassIfExceptionDataMatch() + { + // given + string exceptionMessage = GetRandomString(); + KeyValuePair> randomData = GenerateKeyValuePair(count: 2); + KeyValuePair> expectedData = randomData.DeepClone(); + KeyValuePair> actualData = randomData.DeepClone(); + + var expectedException = new AggregateException( + message: exceptionMessage); + + expectedException.Data.Add( + key: expectedData.Key, + value: expectedData.Value.ToArray()); + + var actualException = new AggregateException( + message: exceptionMessage); + + actualException.Data.Add( + key: actualData.Key, + value: actualData.Value.ToArray()); + + // when + bool result = actualException.SameExceptionAs(expectedException, out string actualMessage); + + // then + Assert.True(result); + Assert.True(String.IsNullOrWhiteSpace(actualMessage)); + } + + [Fact(DisplayName = "06.3 - Aggregate - SameExceptionAsShouldFailIfExceptionDataDontMatch")] + public void AggregateSameExceptionAsShouldFailIfExceptionDataDontMatch() + { + // given + string exceptionMessage = GetRandomString(); + string mutualKey = $"mutual-{GetRandomString()}"; + KeyValuePair> expectedDataOne = GenerateKeyValuePair(count: 1); + KeyValuePair> expectedDataTwo = GenerateKeyValuePair(count: 1); + KeyValuePair> actualData = GenerateKeyValuePair(count: 1); + + KeyValuePair> expectedDataSameKeyName = + GenerateKeyValuePair(count: 1, keyName: mutualKey); + + KeyValuePair> actualDataSameKeyName = + GenerateKeyValuePair(count: 1, keyName: mutualKey); + + var expectedException = new AggregateException( + message: exceptionMessage); + + expectedException.Data.Add( + key: expectedDataOne.Key, + value: expectedDataOne.Value.ToArray()); + + expectedException.Data.Add( + key: expectedDataTwo.Key, + value: expectedDataTwo.Value.ToArray()); + + expectedException.Data.Add( + key: expectedDataSameKeyName.Key, + value: expectedDataSameKeyName.Value.ToArray()); + + var actualException = new AggregateException( + message: exceptionMessage); + + actualException.Data.Add( + key: actualDataSameKeyName.Key, + value: actualDataSameKeyName.Value.ToArray()); + + actualException.Data.Add( + key: actualData.Key, + value: actualData.Value.ToArray()); + + var expectedMessage = new StringBuilder(); + expectedMessage.AppendLine($"Expected exception to:"); + + expectedMessage.AppendLine( + $"- have a data count of {expectedException.Data.Count}, " + + $"but found {actualException.Data.Count}"); + + expectedMessage.AppendLine( + $"- NOT contain key \"{actualData.Key}\""); + + expectedMessage.AppendLine( + $"- contain key \"{expectedDataOne.Key}\" with value(s) ['{expectedDataOne.Value[0]}']"); + + expectedMessage.AppendLine( + $"- contain key \"{expectedDataTwo.Key}\" with value(s) ['{expectedDataTwo.Value[0]}']"); + + expectedMessage.AppendLine( + $"- have key \"{mutualKey}\" with value(s) ['{expectedDataSameKeyName.Value[0]}'], " + + $"but found value(s) ['{actualDataSameKeyName.Value[0]}']"); + + // when + bool result = actualException.SameExceptionAs(expectedException, out string actualMessage); + + //then + Assert.False(result); + actualMessage.Should().BeEquivalentTo(expectedMessage.ToString().Trim()); + } + + [Fact(DisplayName = "07.1 - Aggregate - SameExceptionAsShouldPassIfInnerExceptionMatch")] + public void AggregateSameExceptionAsShouldPassIfInnerExceptionMatchOnType() + { + // given + string randomMessage = GetRandomString(); + Xeption expectedInnerException = new Xeption(message: randomMessage); + Xeption actualInnerException = new Xeption(message: randomMessage); + + var expectedException = new AggregateException( + message: randomMessage, + innerException: expectedInnerException); + + var actualException = new AggregateException( + message: randomMessage, + innerException: actualInnerException); + + // when + bool result = actualException.SameExceptionAs(expectedException, out string actualMessage); + + // then + Assert.True(result); + Assert.True(String.IsNullOrWhiteSpace(actualMessage)); + } + + [Fact(DisplayName = "07.2 - Aggregate - Level 0 - SameExceptionAsShouldFailIfInnerExceptionsTypeDontMatch")] + public void AggregateSameExceptionAsShouldFailIfInnerExceptionsTypeDontMatch() + { + // given + string randomMessage = GetRandomString(); + Xeption expectedInnerException = new Xeption(message: randomMessage); + Exception actualInnerException = new Exception(message: randomMessage); + + var expectedException = new AggregateException( + message: randomMessage, + innerExceptions: expectedInnerException); + + var actualException = new AggregateException( + message: randomMessage, + innerExceptions: actualInnerException); + + var expectedMessage = new StringBuilder(); + expectedMessage.AppendLine($"Aggregate exception differences:"); + + expectedMessage.AppendLine($"* Difference in inner exception at index[0] - Expected exception " + + $"to be \"{expectedInnerException.GetType().FullName}\", " + + $"but found \"{actualInnerException.GetType().FullName}\""); + + // when + bool result = actualException.SameExceptionAs(expectedException, out string actualMessage); + + //then + Assert.False(result); + actualMessage.Should().BeEquivalentTo(expectedMessage.ToString().Trim()); + } + + [Fact(DisplayName = "07.3 - Aggregate - Level 0 - SameExceptionAsShouldFailIfInnerExceptionsMessageDontMatch")] + public void AggregateSameExceptionAsShouldFailIfInnerExceptionsMessageDontMatch() + { + // given + string randomMessage = GetRandomString(); + var expectedInnerException = new Xeption(message: GetRandomString()); + var actualInnerException = new Xeption(message: GetRandomString()); + + var expectedException = new AggregateException( + message: randomMessage, + innerExceptions: expectedInnerException); + + var actualException = new AggregateException( + message: randomMessage, + innerExceptions: actualInnerException); + + var expectedMessage = new StringBuilder(); + expectedMessage.AppendLine($"Aggregate exception differences:"); + + expectedMessage.AppendLine( + $"* Difference in inner exception at index[0] - Expected exception message to be " + + $"\"{expectedInnerException.Message}\", " + + $"but found \"{actualInnerException.Message}\""); + + // when + bool result = actualException.SameExceptionAs(expectedException, out string actualMessage); + + //then + Assert.False(result); + actualMessage.Should().BeEquivalentTo(expectedMessage.ToString().Trim()); + } + + [Fact(DisplayName = "07.4 - Aggregate - Level 0 - SameExceptionAsShouldPassIfInnerExceptionDataMatch")] + public void AggregateSameExceptionAsShouldPassIfInnerExceptionsDataMatch() + { + // given + string exceptionMessage = GetRandomString(); + KeyValuePair> randomData = GenerateKeyValuePair(count: 1); + KeyValuePair> expectedData = randomData.DeepClone(); + KeyValuePair> actualData = randomData.DeepClone(); + + var expectedInnerException = new Xeption( + message: exceptionMessage); + + expectedInnerException.AddData( + key: expectedData.Key, + values: expectedData.Value.ToArray()); + + var actualInnerException = new Xeption( + message: exceptionMessage); + + actualInnerException.AddData( + key: actualData.Key, + values: actualData.Value.ToArray()); + + var expectedException = new AggregateException( + message: exceptionMessage, + innerExceptions: expectedInnerException); + + var actualException = new AggregateException( + message: exceptionMessage, + innerExceptions: actualInnerException); + + // when + bool result = actualException.SameExceptionAs(expectedException, out string actualMessage); + + // then + Assert.True(result); + Assert.True(String.IsNullOrWhiteSpace(actualMessage)); + } + + [Fact(DisplayName = "07.5 - Aggregate - Level 0 - SameExceptionAsShouldFailIfInnerExceptionDataDontMatch")] + public void AggregateSameExceptionAsShouldFailIfInnerExceptionsDataDontMatch() + { + // given + string exceptionMessage = GetRandomString(); + string mutualKey = GetRandomString(); + KeyValuePair> expectedDataOne = GenerateKeyValuePair(count: 1); + KeyValuePair> expectedDataTwo = GenerateKeyValuePair(count: 1); + KeyValuePair> actualData = GenerateKeyValuePair(count: 1); + KeyValuePair> expectedDataSameKeyName = GenerateKeyValuePair(count: 1, mutualKey); + KeyValuePair> actualDataSameKeyName = GenerateKeyValuePair(count: 1, mutualKey); + + var expectedInnerException = new Xeption( + message: exceptionMessage); + + expectedInnerException.AddData( + key: expectedDataOne.Key, + values: expectedDataOne.Value.ToArray()); + + expectedInnerException.AddData( + key: expectedDataTwo.Key, + values: expectedDataTwo.Value.ToArray()); + + expectedInnerException.AddData( + key: expectedDataSameKeyName.Key, + values: expectedDataSameKeyName.Value.ToArray()); + + var actualInnerException = new Xeption( + message: exceptionMessage); + + actualInnerException.AddData( + key: actualDataSameKeyName.Key, + values: actualDataSameKeyName.Value.ToArray()); + + actualInnerException.AddData( + key: actualData.Key, + values: actualData.Value.ToArray()); + + var expectedMessage = new StringBuilder(); + expectedMessage.AppendLine($"Aggregate exception differences:"); + expectedMessage.AppendLine($"* Difference in inner exception at index[0] - Expected exception to:"); + + expectedMessage.AppendLine( + $"- have a data count of {expectedInnerException.Data.Count}, " + + $"but found {actualInnerException.Data.Count}"); + + expectedMessage.AppendLine( + $"- NOT contain key \"{actualData.Key}\""); + + expectedMessage.AppendLine( + $"- contain key \"{expectedDataOne.Key}\" with value(s) ['{expectedDataOne.Value[0]}']"); + + expectedMessage.AppendLine( + $"- contain key \"{expectedDataTwo.Key}\" with value(s) ['{expectedDataTwo.Value[0]}']"); + + expectedMessage.AppendLine( + $"- have key \"{mutualKey}\" with value(s) ['{expectedDataSameKeyName.Value[0]}'], " + + $"but found value(s) ['{actualDataSameKeyName.Value[0]}']"); + + var expectedException = new AggregateException( + message: exceptionMessage, + innerExceptions: expectedInnerException); + + var actualException = new AggregateException( + message: exceptionMessage, + innerExceptions: actualInnerException); + + // when + bool result = actualException.SameExceptionAs(expectedException, out string actualMessage); + + //then + Assert.False(result); + actualMessage.Should().BeEquivalentTo(expectedMessage.ToString().Trim()); + } + + [Fact(DisplayName = "08.1 - Aggregate - Level 1 - SameExceptionAsShouldPassIfInnerInnerExceptionsMatch")] + public void AggregateSameExceptionAsShouldPassIfInnerInnerExceptionsMatch() + { + // given + string randomMessage = GetRandomString(); + Xeption expectedInnerInnerException = new Xeption(message: randomMessage); + Xeption actualInnerInnerException = new Xeption(message: randomMessage); + + Xeption expectedInnerException = new Xeption( + message: randomMessage, + innerException: expectedInnerInnerException); + + Xeption actualInnerException = new Xeption( + message: randomMessage, + innerException: actualInnerInnerException); + + var expectedException = new AggregateException( + message: randomMessage, + innerExceptions: expectedInnerException); + + var actualException = new AggregateException( + message: randomMessage, + innerExceptions: actualInnerException); + + // when + bool result = actualException.SameExceptionAs(expectedException, out string actualMessage); + + // then + Assert.True(result); + Assert.True(String.IsNullOrWhiteSpace(actualMessage)); + } + + [Fact(DisplayName = "08.2 - Aggregate - Level 1 - SameExceptionAsShouldFailIfInnerInnerExceptionsTypeDontMatch")] + public void AggregateSameExceptionAsShouldFailIfInnerInnerExceptionsTypeDontMatch() + { + // given + string randomMessage = GetRandomString(); + Xeption expectedInnerInnerException = new Xeption(message: randomMessage); + Exception actualInnerInnerException = new Exception(message: randomMessage); + + Xeption expectedInnerException = new Xeption( + message: randomMessage, + innerException: expectedInnerInnerException); + + Xeption actualInnerException = new Xeption( + message: randomMessage, + innerException: actualInnerInnerException); + + var expectedException = new AggregateException( + message: randomMessage, + innerExceptions: expectedInnerException); + + var actualException = new AggregateException( + message: randomMessage, + innerExceptions: actualInnerException); + + var expectedMessage = new StringBuilder(); + expectedMessage.AppendLine($"Aggregate exception differences:"); + expectedMessage.AppendLine($"* Difference in inner exception at index[0] - " + + $"Expected inner exception (level 1) to be \"{expectedInnerInnerException.GetType().FullName}\", " + + $"but found \"{actualInnerInnerException.GetType().FullName}\""); + + // when + bool result = actualException.SameExceptionAs(expectedException, out string actualMessage); + + //then + Assert.False(result); + actualMessage.Should().BeEquivalentTo(expectedMessage.ToString().Trim()); + } + + [Fact(DisplayName = "08.3 - Aggregate - Level 1 - SameExceptionAsShouldFailIfInnerInnerExceptionMessageDontMatch")] + public void AggregateSameExceptionAsShouldFailIfInnerInnerExceptionsMessageDontMatch() + { + // given + string randomMessage = GetRandomString(); + var expectedInnerInnerException = new Xeption(message: GetRandomString()); + var actualInnerInnerException = new Xeption(message: GetRandomString()); + + Xeption expectedInnerException = new Xeption( + message: randomMessage, + innerException: expectedInnerInnerException); + + Xeption actualInnerException = new Xeption( + message: randomMessage, + innerException: actualInnerInnerException); + + var expectedException = new AggregateException( + message: randomMessage, + innerExceptions: expectedInnerException); + + var actualException = new AggregateException( + message: randomMessage, + innerExceptions: actualInnerException); + + var expectedMessage = new StringBuilder(); + expectedMessage.AppendLine($"Aggregate exception differences:"); + expectedMessage.AppendLine($"* Difference in inner exception at index[0] - " + + $"Expected inner exception (level 1) message to be \"{expectedInnerInnerException.Message}\", " + + $"but found \"{actualInnerInnerException.Message}\""); + + // when + bool result = actualException.SameExceptionAs(expectedException, out string actualMessage); + + //then + Assert.False(result); + actualMessage.Should().BeEquivalentTo(expectedMessage.ToString().Trim()); + } + + [Fact(DisplayName = "08.4 - Aggregate - Level 1 - SameExceptionAsShouldPassIfInnerInnerExceptionDataMatch")] + public void AggregateSameExceptionAsShouldPassIfInnerInnerExceptionsDataMatch() + { + // given + string exceptionMessage = GetRandomString(); + KeyValuePair> randomData = GenerateKeyValuePair(count: 1); + KeyValuePair> expectedData = randomData.DeepClone(); + KeyValuePair> actualData = randomData.DeepClone(); + + var expectedInnerInnerException = new Xeption( + message: exceptionMessage); + + expectedInnerInnerException.AddData( + key: expectedData.Key, + values: expectedData.Value.ToArray()); + + var actualInnerInnerException = new Xeption( + message: exceptionMessage); + + actualInnerInnerException.AddData( + key: actualData.Key, + values: actualData.Value.ToArray()); + + Xeption expectedInnerException = new Xeption( + message: exceptionMessage, + innerException: expectedInnerInnerException); + + Xeption actualInnerException = new Xeption( + message: exceptionMessage, + innerException: actualInnerInnerException); + + var expectedException = new AggregateException( + message: exceptionMessage, + innerExceptions: expectedInnerException); + + var actualException = new AggregateException( + message: exceptionMessage, + innerExceptions: actualInnerException); + + // when + bool result = actualException.SameExceptionAs(expectedException, out string actualMessage); + + // then + Assert.True(result); + Assert.True(String.IsNullOrWhiteSpace(actualMessage)); + } + + [Fact(DisplayName = "08.5 - Aggregate - Level 1 - SameExceptionAsShouldFailIfInnerInnerExceptionDataDontMatch")] + public void AggregateSameExceptionAsShouldFailIfAggregateInnerInnerExceptionDataDontMatch() + { + // given + string exceptionMessage = GetRandomString(); + string mutualKey = GetRandomString(); + KeyValuePair> expectedDataOne = GenerateKeyValuePair(count: 1); + KeyValuePair> expectedDataTwo = GenerateKeyValuePair(count: 1); + KeyValuePair> actualData = GenerateKeyValuePair(count: 1); + KeyValuePair> expectedDataSameKeyName = GenerateKeyValuePair(count: 1, mutualKey); + KeyValuePair> actualDataSameKeyName = GenerateKeyValuePair(count: 1, mutualKey); + + var expectedInnerInnerException = new Xeption( + message: exceptionMessage); + + expectedInnerInnerException.AddData( + key: expectedDataOne.Key, + values: expectedDataOne.Value.ToArray()); + + expectedInnerInnerException.AddData( + key: expectedDataTwo.Key, + values: expectedDataTwo.Value.ToArray()); + + expectedInnerInnerException.AddData( + key: expectedDataSameKeyName.Key, + values: expectedDataSameKeyName.Value.ToArray()); + + var actualInnerInnerException = new Xeption( + message: exceptionMessage); + + actualInnerInnerException.AddData( + key: actualDataSameKeyName.Key, + values: actualDataSameKeyName.Value.ToArray()); + + actualInnerInnerException.AddData( + key: actualData.Key, + values: actualData.Value.ToArray()); + + Xeption expectedInnerException = new Xeption( + message: exceptionMessage, + innerException: expectedInnerInnerException); + + Xeption actualInnerException = new Xeption( + message: exceptionMessage, + innerException: actualInnerInnerException); + + var expectedMessage = new StringBuilder(); + expectedMessage.AppendLine($"Aggregate exception differences:"); + + expectedMessage.AppendLine( + $"* Difference in inner exception at index[0] - " + + $"Expected inner exception (level 1) to:"); + + expectedMessage.AppendLine( + $"- have a data count of {expectedInnerInnerException.Data.Count}, " + + $"but found {actualInnerInnerException.Data.Count}"); + + expectedMessage.AppendLine( + $"- NOT contain key \"{actualData.Key}\""); + + expectedMessage.AppendLine( + $"- contain key \"{expectedDataOne.Key}\" with value(s) ['{expectedDataOne.Value[0]}']"); + + expectedMessage.AppendLine( + $"- contain key \"{expectedDataTwo.Key}\" with value(s) ['{expectedDataTwo.Value[0]}']"); + + expectedMessage.AppendLine( + $"- have key \"{mutualKey}\" with value(s) ['{expectedDataSameKeyName.Value[0]}'], " + + $"but found value(s) ['{actualDataSameKeyName.Value[0]}']"); + + var expectedException = new AggregateException( + message: exceptionMessage, + innerExceptions: expectedInnerException); + + var actualException = new AggregateException( + message: exceptionMessage, + innerExceptions: actualInnerException); + + // when + bool result = actualException.SameExceptionAs(expectedException, out string actualMessage); + + //then + Assert.False(result); + actualMessage.Should().BeEquivalentTo(expectedMessage.ToString().Trim()); + } } } diff --git a/Xeption.Tests/XeptionExtensionTests.cs b/Xeption.Tests/XeptionExtensionTests.cs index 8e95d81..19350f0 100644 --- a/Xeption.Tests/XeptionExtensionTests.cs +++ b/Xeption.Tests/XeptionExtensionTests.cs @@ -3,6 +3,8 @@ // ---------------------------------------------------------------------------------- using System; +using System.Collections.Generic; +using System.Linq; using Tynamix.ObjectFiller; namespace Xeptions.Tests @@ -12,6 +14,24 @@ public partial class XeptionExtensionTests private static string GetRandomString() => new MnemonicString().GetValue(); + private static KeyValuePair> GenerateKeyValuePair(int count, string keyName = "") + { + if (String.IsNullOrWhiteSpace(keyName)) + { + keyName = GetRandomString(); + } + + List values = Enumerable.Range(start: 0, count) + .Select(_ => GetRandomString()) + .ToList(); + + var keyValuePair = new KeyValuePair>( + key: keyName, + value: values); + + return keyValuePair; + } + internal class OtherTarget { public static void ThrowingExceptionMethod() => diff --git a/Xeption.Tests/XeptionTests.Logic.DataEqualsWithMessage.cs b/Xeption.Tests/XeptionTests.Logic.DataEqualsWithMessage.cs index c098116..1fb4c62 100644 --- a/Xeption.Tests/XeptionTests.Logic.DataEqualsWithMessage.cs +++ b/Xeption.Tests/XeptionTests.Logic.DataEqualsWithMessage.cs @@ -33,7 +33,7 @@ public void ShouldReturnTrueAndNullStringIfDataMatchInCount() } [Fact] - public void ShouldReturnFalsAndMessageStringIfActualDataContainsKeysNotInExpectedData() + public void ShouldReturnFalseAndMessageStringIfActualDataContainsKeysNotInExpectedData() { // given Xeption randomXeption = new Xeption(); @@ -49,13 +49,13 @@ public void ShouldReturnFalsAndMessageStringIfActualDataContainsKeysNotInExpecte actualXeption.UpsertDataList(randomKey, randomValue); var expectedMessage = new StringBuilder(); - expectedMessage.AppendLine($"Expected data to: "); + expectedMessage.AppendLine($"Expected exception to:"); expectedMessage.AppendLine( - $"- have a count of {expectedXeption.Data.Count}, " + - $"but found {actualXeption.Data.Count}."); + $"- have a data count of {expectedXeption.Data.Count}, " + + $"but found {actualXeption.Data.Count}"); - expectedMessage.AppendLine($"- NOT contain key '{randomKey}'."); + expectedMessage.AppendLine($"- NOT contain key \"{randomKey}\""); // when (bool isEqual, string message) = actualXeption.DataEqualsWithDetail(expectedXeption.Data); @@ -63,7 +63,7 @@ public void ShouldReturnFalsAndMessageStringIfActualDataContainsKeysNotInExpecte // then isEqual.Should().BeFalse(); message.Should().NotBeNullOrEmpty(); - message.Should().BeEquivalentTo(expectedMessage.ToString()); + message.Should().BeEquivalentTo(expectedMessage.ToString().Trim()); } [Fact] @@ -83,14 +83,14 @@ public void ShouldReturnFalseAndMessageStringIfExpectedDataContainsKeysNotInActu expectedXeption.UpsertDataList(randomKey, randomValue); var expectedMessage = new StringBuilder(); - expectedMessage.AppendLine($"Expected data to: "); + expectedMessage.AppendLine($"Expected exception to:"); expectedMessage.AppendLine( - $"- have a count of {expectedXeption.Data.Count}, " + - $"but found {actualXeption.Data.Count}."); + $"- have a data count of {expectedXeption.Data.Count}, " + + $"but found {actualXeption.Data.Count}"); expectedMessage.AppendLine( - $"- contain key '{randomKey}' with value(s) [{randomValue}]."); + $"- contain key \"{randomKey}\" with value(s) ['{randomValue}']"); // when (bool isEqual, string message) = actualXeption @@ -99,11 +99,11 @@ public void ShouldReturnFalseAndMessageStringIfExpectedDataContainsKeysNotInActu // then isEqual.Should().BeFalse(); message.Should().NotBeNullOrEmpty(); - message.Should().BeEquivalentTo(expectedMessage.ToString()); + message.Should().BeEquivalentTo(expectedMessage.ToString().Trim('\r', '\n')); } [Fact] - public void ShouldReturnFalseAndMessageStringIfExpectedDataContainsKeysMatchingKeysWithUnmatchedValues() + public void ShouldReturnFalseAndMessageStringIfExpectedDataContainsKeysWithUnmatchedValues() { // given Xeption randomXeption = new Xeption(); @@ -127,11 +127,11 @@ public void ShouldReturnFalseAndMessageStringIfExpectedDataContainsKeysMatchingK .Select(value => value).Aggregate((t1, t2) => t1 + "','" + t2); var expectedMessage = new StringBuilder(); - expectedMessage.AppendLine($"Expected data to: "); + expectedMessage.AppendLine($"Expected exception to:"); expectedMessage.AppendLine( - $"- have key '{randomKey}' with value(s) ['{expectedValues}'], " + - $"but found value(s) ['{actualValues}']."); + $"- have key \"{randomKey}\" with value(s) ['{expectedValues}'], " + + $"but found value(s) ['{actualValues}']"); // when (bool isEqual, string message) = actualXeption @@ -140,7 +140,7 @@ public void ShouldReturnFalseAndMessageStringIfExpectedDataContainsKeysMatchingK // then isEqual.Should().BeFalse(); message.Should().NotBeNullOrEmpty(); - message.Should().BeEquivalentTo(expectedMessage.ToString()); + message.Should().BeEquivalentTo(expectedMessage.ToString().Trim('\r', '\n')); } } } \ No newline at end of file diff --git a/Xeption/Assertions/XeptionAssertions.cs b/Xeption/Assertions/XeptionAssertions.cs index 5a0d9e5..fff3b49 100644 --- a/Xeption/Assertions/XeptionAssertions.cs +++ b/Xeption/Assertions/XeptionAssertions.cs @@ -3,9 +3,6 @@ // ---------------------------------------------------------------------------------- using System; -using System.Collections; -using System.Collections.Generic; -using System.Text; using FluentAssertions.Execution; using FluentAssertions.Primitives; using Xeptions; @@ -23,244 +20,22 @@ public AndConstraint> BeEquivalentTo( string because = "", params object[] becauseArgs) { - var actualException = Subject == null - ? new Xeption() - : new Xeption(Subject.Message, Subject.InnerException, Subject.Data); + Exception actualException = Subject; + Exception expectedException = expectation; - if (Subject is AggregateException actualAggregate && expectation is AggregateException expectedAggregate) - { - Execute.Assertion - .BecauseOf(because, becauseArgs) - .WithExpectation("Expected the ") - .Given(() => Subject) + bool isMatch = XeptionExtensions + .IsSameExceptionsAs(actualException, expectedException, out string message); - .ForCondition(subject => - InnerExceptionsMatch(subject, expectation, "aggregate exception", because, becauseArgs)) - - .FailWith("to be equivalent to {0}{reason}, but it was not.", expectation) - .Then - .ClearExpectation(); - } - else - { - Execute.Assertion - .BecauseOf(because, becauseArgs) - .WithExpectation("Expected the ") - .Given(() => Subject) - - .ForCondition(subject => - InnerExceptionsMatch(subject, expectation, "exception", because, becauseArgs)) - - .FailWith("to be equivalent to {0}{reason}, but it was not.", expectation) - .Then - .ClearExpectation(); - } + Execute.Assertion + .ForCondition(isMatch) + .BecauseOf(because, becauseArgs) + .FailWith(message) + .Then + .ClearExpectation(); return new AndConstraint>(this); } - private bool InnerExceptionsMatch( - Exception actual, - Exception expected, - string type, - string because, - params object[] becauseArgs) - { - if (actual is null && expected is null) - return true; - - if (actual is null || expected is null) - { - Execute.Assertion - .BecauseOf(because, becauseArgs) - .FailWith( - $"Expected {type} exception to be \"{expected?.GetType().FullName ?? "null"}\", " + - $"but found \"{actual?.GetType().FullName ?? "null"}\"."); - - return false; - } - - bool typeMatch = actual.GetType().FullName == expected.GetType().FullName; - bool messageMatch = actual.Message == expected.Message; - - if (actual is AggregateException actualAggregate && expected is AggregateException expectedAggregate) - { - Execute.Assertion - .BecauseOf(because, becauseArgs) - .ForCondition(typeMatch) - - .FailWith( - $"Expected aggregate {type} type to be \"{expected.GetType().FullName}\", " + - $"but found \"{actual.GetType().FullName}\".") - - .Then - .ForCondition(messageMatch) - - .FailWith( - $"Expected aggregate {type} message to be \"{expected.Message}\", " + - $"but found \"{actual.Message}\".") - - .Then - - .ForCondition( - ExceptionDataMatch(actual, expected, "aggregate inner exception", because, becauseArgs)) - - .FailWith("data to match but it does not.") - .Then - .ClearExpectation(); - - var actualInnerExceptions = actualAggregate.InnerExceptions; - var expectedInnerExceptions = expectedAggregate.InnerExceptions; - - bool countMatch = actualInnerExceptions.Count == expectedInnerExceptions.Count; - - if (countMatch is false) - { - Execute.Assertion - .BecauseOf(because, becauseArgs) - .ForCondition(countMatch) - .FailWith( - "Expected {0} count to be {1}, but found {2}.", - type, - expectedInnerExceptions.Count, - actualInnerExceptions.Count); - - return false; - } - - if (countMatch) - { - for (int i = 0; i < actualInnerExceptions.Count; i++) - { - if (InnerExceptionsMatch( - actual: actualInnerExceptions[i], - expected: expectedInnerExceptions[i], - type: $"aggregate inner exception [{i}]", - because, - becauseArgs) != true) - { - return false; - } - } - } - } - else - { - Execute.Assertion - .BecauseOf(because, becauseArgs) - .ForCondition(typeMatch) - - .FailWith( - $"Expected {type} type to be \"{expected.GetType().FullName}\", " + - $"but found \"{actual.GetType().FullName}\".") - - .Then - .ForCondition(messageMatch) - .FailWith($"Expected {type} message to be \"{expected.Message}\", but found \"{actual.Message}\".") - .Then - .ForCondition(ExceptionDataMatch(actual, expected, type, because, becauseArgs)) - .FailWith("data to match but it does not.") - .Then - .ClearExpectation(); - - return InnerExceptionsMatch( - actual: actual.InnerException, - expected: expected.InnerException, - type: type == "aggregate inner exception" ? "aggregate inner exception" : "inner exception", - because, - becauseArgs); - } - - return true; - } - - private bool ExceptionDataMatch( - Exception actual, - Exception expected, - string type, - string because, - params object[] becauseArgs) - { - - IDictionary actualData = actual.Data; - IDictionary expectedData = expected.Data; - - if (actualData == null && expectedData == null) - return true; - - if (actualData == null || expectedData == null) - { - Execute.Assertion - .BecauseOf(because, becauseArgs) - .FailWith( - $"Expected {type} with type '{expected.GetType().FullName}' " + - $"to have data as {0}, but found {1}.", - expectedData?.Count == null ? "null" : "not null", - actualData?.Count == null ? "null" : "not null"); - - return false; - } - - var dataSummary = new StringBuilder(); - - if (actualData.Count != expectedData.Count) - { - dataSummary.AppendLine( - $"- have an expected data item count to be {expectedData.Count}, " + - $"but found {actualData.Count}."); - } - - foreach (var key in expectedData.Keys) - { - if (actualData.Contains(key) is false) - { - var expectedValues = String.Join(", ", expectedData[key] as List); - - dataSummary.AppendLine( - $"- contain key '{key}' with value(s) [{expectedValues}]."); - } - } - - foreach (var key in actualData.Keys) - { - if (expectedData.Contains(key) is false) - { - dataSummary.AppendLine( - $"- NOT contain key '{key}'."); - } - } - - foreach (var key in expectedData.Keys) - { - if (actualData.Contains(key)) - { - var actualValues = String.Join(", ", actualData[key] as List); - var expectedValues = String.Join(", ", expectedData[key] as List); - - if (Equals(actualValues, expectedValues) is false) - { - dataSummary.AppendLine( - $"- have key '{key}' with value(s) [{expectedValues}], " + - $"but found value(s) [{actualValues}]."); - } - } - } - - if (dataSummary.Length > 0) - { - string errorMessage = $"Expected {type} to:{Environment.NewLine}" + - $"{dataSummary.ToString()}"; - - Execute.Assertion - .BecauseOf(because, becauseArgs) - .FailWith(errorMessage); - - return false; - } - - return true; - } - protected override string Identifier => "Exception"; } } diff --git a/Xeption/Xeption.cs b/Xeption/Xeption.cs index 8e7672f..d5550c2 100644 --- a/Xeption/Xeption.cs +++ b/Xeption/Xeption.cs @@ -76,133 +76,170 @@ public bool DataEquals(IDictionary dictionary) public (bool IsEqual, string Message) DataEqualsWithDetail(IDictionary dictionary) { - bool isEqual = true; - var messageStringBuilder = new StringBuilder(); - isEqual = CompareDataKeys(dictionary, isEqual, messageStringBuilder); + (bool isEqual, string error) = CompareDataKeys(this.Data, dictionary); - string errorMessage = String.IsNullOrWhiteSpace(messageStringBuilder.ToString()) - ? String.Empty - : $"Expected data to: {Environment.NewLine}{messageStringBuilder.ToString()}"; - - return (isEqual, errorMessage); + return (isEqual, error); } - private bool CompareDataKeys(IDictionary dictionary, bool isEqual, StringBuilder messageStringBuilder) + internal static (bool IsMatch, string Message) CompareDataKeys( + IDictionary dictionary, + IDictionary otherDictionary, + int exceptionLevel = 0) { - if (this.Data.Count == 0 && dictionary.Count == 0) + if (dictionary.Count == 0 && otherDictionary.Count == 0) { - return isEqual; + return (true, String.Empty); } - if (this.Data.Count != dictionary.Count) - { - isEqual = false; + string exceptionLevelName = exceptionLevel == 0 + ? "exception" + : $"inner exception (level {exceptionLevel})"; + + bool isMatch = true; + var errors = new StringBuilder(); + errors.AppendLine($"Expected {exceptionLevelName} to:"); + bool unmatched = dictionary.Count != otherDictionary.Count; - AppendMessage( - messageStringBuilder, - $"- have a count of {dictionary.Count}, but found {this.Data.Count}."); + if (dictionary.Count != otherDictionary.Count) + { + errors.AppendLine($"- have a data count of {otherDictionary.Count}, but found {dictionary.Count}"); } (IDictionary additionalItems, IDictionary missingItems, IDictionary sharedItems) = - GetDataDifferences(dictionary); + GetDataDifferences(dictionary, otherDictionary); - isEqual = EvaluateAdditionalKeys(isEqual, messageStringBuilder, additionalItems); - isEqual = EvaluateMissingKeys(isEqual, messageStringBuilder, missingItems); - isEqual = EvaluateSharedKeys(isEqual, messageStringBuilder, sharedItems); + (bool hasAdditionalItems, string additionalErrors) = EvaluateAdditionalKeys(additionalItems); + (bool hasMissingItems, string missingErrors) = EvaluateMissingKeys(missingItems); + (bool unMatchedItems, string unMatchedItemsErrors) = EvaluateSharedKeys(dictionary, sharedItems); - return isEqual; + if (unmatched || hasAdditionalItems || hasMissingItems || unMatchedItems) + { + if (String.IsNullOrWhiteSpace(additionalErrors) is false) + { + errors.AppendLine(additionalErrors); + } + + if (String.IsNullOrWhiteSpace(missingErrors) is false) + { + errors.AppendLine(missingErrors); + } + + if (String.IsNullOrWhiteSpace(unMatchedItemsErrors) is false) + { + errors.AppendLine(unMatchedItemsErrors); + } + + return (false, errors.ToString().Trim()); + } + + return (true, string.Empty); } - private bool EvaluateAdditionalKeys( - bool isEqual, - StringBuilder messageStringBuilder, + private static (bool hasAdditionalItems, string additionalErrors) EvaluateAdditionalKeys( IDictionary additionalItems) { + bool hasAdditionalItems = additionalItems?.Count > 0; + var additionalErrors = new StringBuilder(); + if (additionalItems?.Count > 0) { - isEqual = false; + hasAdditionalItems = true; foreach (DictionaryEntry dictionaryEntry in additionalItems) { - AppendMessage( - messageStringBuilder, - $"- NOT contain key '{dictionaryEntry.Key}'."); + additionalErrors.AppendLine($"- NOT contain key \"{dictionaryEntry.Key}\""); } } - return isEqual; + return (hasAdditionalItems, additionalErrors.ToString().Trim()); } - private bool EvaluateMissingKeys( - bool isEqual, - StringBuilder messageStringBuilder, - IDictionary missingItems) + private static (bool hasMissingItems, string missingErrors) EvaluateMissingKeys(IDictionary missingItems) { + bool hasMissingItems = missingItems?.Count > 0; + var missingErrors = new StringBuilder(); + if (missingItems?.Count > 0) { - isEqual = false; - foreach (DictionaryEntry dictionaryEntry in missingItems) { - var values = String.Join(", ", dictionaryEntry.Value as List); - - AppendMessage( - messageStringBuilder, - $"- contain key '{dictionaryEntry.Key}' with value(s) [{values}]."); + var values = GetDictionaryValues(dictionaryEntry.Value); + missingErrors.AppendLine($"- contain key \"{dictionaryEntry.Key}\" with value(s) ['{values}']"); } } - return isEqual; + return (hasMissingItems, missingErrors.ToString().Trim()); } - private bool EvaluateSharedKeys( - bool isEqual, - StringBuilder messageStringBuilder, + private static (bool unMatchedItems, string unMatchedItemsErrors) EvaluateSharedKeys( + IDictionary dictionary, IDictionary sharedItems) { if (sharedItems?.Count > 0) { + bool unMatchedItems = false; + var unMatchedItemsErrors = new StringBuilder(); + foreach (DictionaryEntry dictionaryEntry in sharedItems) { - - string expectedValues = ((List)dictionaryEntry.Value) - .Select(value => value).Aggregate((t1, t2) => t1 + "','" + t2); - - string actualValues = ((List)this.Data[dictionaryEntry.Key]) - .Select(value => value).Aggregate((t1, t2) => t1 + "','" + t2); + string expectedValues = GetDictionaryValues(dictionaryEntry.Value); + string actualValues = GetDictionaryValues(dictionary[dictionaryEntry.Key]); if (actualValues != expectedValues) { - isEqual = false; + unMatchedItems = true; - AppendMessage( - messageStringBuilder, - $"- have key '{dictionaryEntry.Key}' " + + unMatchedItemsErrors.AppendLine( + $"- have key \"{dictionaryEntry.Key}\" " + $"with value(s) ['{expectedValues}'], " + - $"but found value(s) ['{actualValues}']."); + $"but found value(s) ['{actualValues}']"); } } + + return (unMatchedItems, unMatchedItemsErrors.ToString().Trim()); } - return isEqual; + return (false, string.Empty); + } + + private static string GetDictionaryValues(object values) + { + List valuesList; + + if (values is string[]) + { + valuesList = ((string[])values).ToList(); + } + else if (values is List) + { + valuesList = (List)values; + } + else + { + throw new InvalidCastException("Unsupported type in sharedItems dictionary."); + } + + string stringValues = valuesList.Aggregate((t1, t2) => t1 + "','" + t2); + + return stringValues; } - private ( + private static ( IDictionary AdditionalItems, IDictionary MissingItems, IDictionary SharedItems) - GetDataDifferences(IDictionary dictionary) + GetDataDifferences(IDictionary dictionary, IDictionary otherDictionary) { - IDictionary additionalItems = this.Data.DeepClone(); - IDictionary missingItems = dictionary.DeepClone(); - IDictionary sharedItems = dictionary.DeepClone(); + IDictionary additionalItems = dictionary.DeepClone(); + IDictionary missingItems = otherDictionary.DeepClone(); + IDictionary sharedItems = otherDictionary.DeepClone(); - foreach (DictionaryEntry dictionaryEntry in dictionary) + foreach (DictionaryEntry dictionaryEntry in otherDictionary) { additionalItems.Remove(dictionaryEntry.Key); } - foreach (DictionaryEntry dictionaryEntry in this.Data) + foreach (DictionaryEntry dictionaryEntry in dictionary) { missingItems.Remove(dictionaryEntry.Key); } @@ -219,13 +256,5 @@ private bool EvaluateSharedKeys( return (additionalItems, missingItems, sharedItems); } - - private void AppendMessage(StringBuilder builder, string message) - { - if (String.IsNullOrEmpty(message) is false) - { - builder.AppendLine(message); - } - } } } \ No newline at end of file diff --git a/Xeption/Xeption.csproj b/Xeption/Xeption.csproj index c2efadd..5d76ec6 100644 --- a/Xeption/Xeption.csproj +++ b/Xeption/Xeption.csproj @@ -1,7 +1,7 @@  - netstandard2.0;netstandard2.1 + net9.0;netstandard2.0;netstandard2.1 Hassan Habib, Christo du Toit Hassan Habib Exceptional Exceptions @@ -10,18 +10,18 @@ true Xeption.png - 2.7 - 2.7.0.0 - 2.7.0.0 + 2.8 + 2.8.0.0 + 2.8.0.0 en-US https://www.xeption.net License.txt https://github.com/hassanhabib/Xeption - Github + GitHub Exceptions .NET - Addition of a SameExceptionAs overload that can provide detail on differnces. + Addition of a SameExceptionAs overload that can provide detail on differences. Aggregate exception support. @@ -39,7 +39,7 @@ - + diff --git a/Xeption/XeptionExtensions.cs b/Xeption/XeptionExtensions.cs index 2e69077..6de35a7 100644 --- a/Xeption/XeptionExtensions.cs +++ b/Xeption/XeptionExtensions.cs @@ -4,7 +4,7 @@ using System; using System.Linq.Expressions; -using FluentAssertions; +using System.Text; namespace Xeptions { @@ -16,38 +16,121 @@ public static bool IsFrom(this Exception exception, string origin) => public static bool IsNotFrom(this Exception exception, string origin) => exception.TargetSite?.ReflectedType?.Name != origin; - public static bool SameExceptionAs(this Exception exception, Exception otherException) + public static bool SameExceptionAs(this Exception exception, Exception otherException) => + IsSameExceptionsAs(exception, otherException, out string message); + + public static bool SameExceptionAs(this Exception exception, Exception otherException, out string message) => + IsSameExceptionsAs(exception, otherException, out message); + + public static Expression> SameExceptionAs(Exception expectedException) => + actualException => actualException.SameExceptionAs(expectedException); + + internal static bool IsSameExceptionsAs( + Exception exception, + Exception otherException, + out string message, + int exceptionLevel = 0) { - try + string exceptionLevelName = exceptionLevel == 0 + ? "exception" + : $"inner exception (level {exceptionLevel})"; + + if (exception is null && otherException is null) { - exception.Should().BeEquivalentTo(otherException); + message = String.Empty; return true; } - catch (Exception) + + if (exception is null || otherException is null) { + message = $"Expected {exceptionLevelName} to be \"{otherException?.GetType()?.FullName ?? "null"}\", " + + $"but found \"{exception?.GetType()?.FullName ?? "null"}\""; + return false; } - } - public static bool SameExceptionAs(this Exception exception, Exception otherException, out string message) - { - try + var errors = new StringBuilder(); + bool invalidException = false; + + if (exception.GetType().FullName != otherException.GetType().FullName) { - exception.Should().BeEquivalentTo(otherException); - message = String.Empty; + invalidException = true; - return true; + errors.AppendLine($"Expected {exceptionLevelName} to be " + + $"\"{otherException?.GetType()?.FullName ?? "null"}\", " + + $"but found \"{exception?.GetType()?.FullName ?? "null"}\""); + } + + if ((exception is AggregateException + && otherException is AggregateException) is false) + { + if (exception.Message != otherException.Message) + { + invalidException = true; + + errors.AppendLine($"Expected {exceptionLevelName} message to be \"{otherException.Message}\", " + + $"but found \"{exception.Message}\""); + } } - catch (Exception error) + + (bool isDataEqual, string dataMessage) = + Xeption.CompareDataKeys(exception.Data, otherException.Data, exceptionLevel); + + if (isDataEqual == false) { - message = error.Message; - + invalidException = true; + errors.AppendLine(dataMessage); + } + + if (invalidException == true) + { + message = errors.ToString().Trim(); return false; } - } - public static Expression> SameExceptionAs(Exception expectedException) => - actualException => actualException.SameExceptionAs(expectedException); + if (exception is AggregateException aggregateException + && otherException is AggregateException otherAggregateException) + { + if (aggregateException.InnerExceptions.Count != otherAggregateException.InnerExceptions.Count) + { + message = + $"Expected aggregate exception to contain " + + $"{otherAggregateException.InnerExceptions.Count} inner exception(s), " + + $"but found {aggregateException.InnerExceptions.Count}"; + + return false; + } + else + { + var aggregateErrors = new StringBuilder(); + aggregateErrors.AppendLine("Aggregate exception differences:"); + + for (int i = 0; i < aggregateException.InnerExceptions.Count; i++) + { + if (!aggregateException.InnerExceptions[i].SameExceptionAs( + otherAggregateException.InnerExceptions[i], out string innerMessage)) + { + invalidException = true; + + aggregateErrors + .AppendLine($"* Difference in inner exception at index[{i}] - {innerMessage}"); + } + } + + if (invalidException == true) + { + message = aggregateErrors.ToString().Trim(); + return false; + } + } + } + + return IsSameExceptionsAs( + exception.InnerException, + otherException.InnerException, + out message, + exceptionLevel + 1); + } } }