From 2b0c6a3fade8ff25988e817d5cb7f5d70681046b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andreas=20=C3=96hlund?= Date: Fri, 10 Apr 2026 08:51:13 +0200 Subject: [PATCH] Restore properties after encryption to support them being reused (#713) * Restore properties after encryption to support reuse * Switch to new tuple syntax * Better name * Use existing tests instead * Add test to check correctness when sending the same object multiple times * Minimize diff revert EOF changes * Test both supported variants --------- Co-authored-by: Ramon Smits --- .../When_sending_the_same_message_twice.cs | 76 +++++++ .../When_using_Aes_with_custom.cs | 172 +++++++-------- .../When_using_Aes_with_unobtrusive_mode.cs | 207 ++++++++---------- src/MessageProperty/DecryptBehavior.cs | 4 +- src/MessageProperty/EncryptBehavior.cs | 57 +++-- src/MessageProperty/EncryptionInspector.cs | 10 +- .../ConventionBasedEncryptedStringSpecs.cs | 4 +- src/Tests/WireEncryptedStringSpecs.cs | 52 +++-- 8 files changed, 325 insertions(+), 257 deletions(-) create mode 100644 src/AcceptanceTests/When_sending_the_same_message_twice.cs diff --git a/src/AcceptanceTests/When_sending_the_same_message_twice.cs b/src/AcceptanceTests/When_sending_the_same_message_twice.cs new file mode 100644 index 00000000..4a735dc6 --- /dev/null +++ b/src/AcceptanceTests/When_sending_the_same_message_twice.cs @@ -0,0 +1,76 @@ +namespace NServiceBus.Encryption.MessageProperty.AcceptanceTests; + +using System.Collections.Generic; +using System.Threading.Tasks; +using AcceptanceTesting; +using NUnit.Framework; + +public class When_sending_the_same_message_twice : NServiceBusAcceptanceTest +{ + [Test] + public async Task Should_not_corrupt_encrypted_properties() + { + var secret = "betcha can't guess my secret"; + var messageToReuse = new MessageWithSecretData + { + Secret = secret, + EncryptedString = secret, + SubProperty = new MySecretSubProperty { Secret = secret } + }; + + var context = await Scenario.Define() + .WithEndpoint(b => b.When(async session => + { + await session.SendLocal(messageToReuse); + await session.SendLocal(messageToReuse); + })) + .Done(c => c.MessagesReceived.Count == 2) + .Run(); + + Assert.Multiple(() => + { + foreach (var message in context.MessagesReceived) + { + Assert.That(message.Secret.Value, Is.EqualTo(secret)); + Assert.That(message.EncryptedString, Is.EqualTo(secret)); + Assert.That(message.SubProperty.Secret.Value, Is.EqualTo(secret)); + } + }); + } + + public class Context : ScenarioContext + { + public List MessagesReceived { get; } = []; + } + + public class Endpoint : EndpointConfigurationBuilder + { + public Endpoint() => EndpointSetup(config => + { + var encryptionService = new AesEncryptionService("1st", new Dictionary { { "1st", "gdDbqRpqdRbTs3mhdZh9qCaDaxJXl+e6"u8.ToArray() } }); + config.EnableMessagePropertyEncryption(encryptionService, property => property.Name.StartsWith("Encrypted") || property.PropertyType == typeof(EncryptedString)); + }); + + public class Handler(Context testContext) : IHandleMessages + { + public Task Handle(MessageWithSecretData message, IMessageHandlerContext context) + { + testContext.MessagesReceived.Add(message); + + return Task.FromResult(0); + } + } + } + + public class MessageWithSecretData : IMessage + { + public EncryptedString Secret { get; set; } + public MySecretSubProperty SubProperty { get; set; } + public string EncryptedString { get; set; } + } + + public class MySecretSubProperty + { + public EncryptedString Secret { get; set; } + } +} \ No newline at end of file diff --git a/src/AcceptanceTests/When_using_Aes_with_custom.cs b/src/AcceptanceTests/When_using_Aes_with_custom.cs index 9585aa11..5a0b191a 100644 --- a/src/AcceptanceTests/When_using_Aes_with_custom.cs +++ b/src/AcceptanceTests/When_using_Aes_with_custom.cs @@ -1,118 +1,108 @@ -namespace NServiceBus.Encryption.MessageProperty.AcceptanceTests +namespace NServiceBus.Encryption.MessageProperty.AcceptanceTests; + +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using AcceptanceTesting; +using NUnit.Framework; + +public class When_using_Aes_with_custom : NServiceBusAcceptanceTest { - using System; - using System.Collections.Generic; - using System.Text; - using System.Threading.Tasks; - using AcceptanceTesting; - using NUnit.Framework; - - public class When_using_Aes_with_custom : NServiceBusAcceptanceTest + [Test] + public async Task Should_receive_decrypted_message() { - [Test] - public async Task Should_receive_decrypted_message() + var messageToSend = new MessageWithSecretData { - var context = await Scenario.Define() - .WithEndpoint(b => b.When(session => session.SendLocal(new MessageWithSecretData + Secret = "betcha can't guess my secret", + SubProperty = new MySecretSubProperty { Secret = "My sub secret" }, + CreditCards = + [ + new CreditCardDetails + { + ValidTo = DateTime.UtcNow.AddYears(1), + Number = "312312312312312" + }, + new CreditCardDetails { - Secret = "betcha can't guess my secret", - SubProperty = new MySecretSubProperty - { - Secret = "My sub secret" - }, - CreditCards = - [ - new CreditCardDetails - { - ValidTo = DateTime.UtcNow.AddYears(1), - Number = "312312312312312" - }, - new CreditCardDetails - { - ValidTo = DateTime.UtcNow.AddYears(2), - Number = "543645546546456" - } - ] - }))) - .Done(c => c.GetTheMessage) - .Run(); - - Assert.AreEqual("betcha can't guess my secret", context.Secret); - Assert.AreEqual("My sub secret", context.SubPropertySecret); - CollectionAssert.AreEquivalent(new List + ValidTo = DateTime.UtcNow.AddYears(2), + Number = "543645546546456" + } + ] + }; + + var context = await Scenario.Define() + .WithEndpoint(b => b.When(session => session.SendLocal(messageToSend))) + .Done(c => c.GetTheMessage) + .Run(); + + Assert.Multiple(() => + { + Assert.That(context.Secret, Is.EqualTo(messageToSend.Secret.Value)); + Assert.That(context.SubPropertySecret, Is.EqualTo(messageToSend.SubProperty.Secret.Value)); + Assert.That(context.CreditCards, Is.EquivalentTo(new List() { "312312312312312", "543645546546456" - }, context.CreditCards); - } + })); + }); + } - public class Context : ScenarioContext - { - public bool GetTheMessage { get; set; } + public class Context : ScenarioContext + { + public bool GetTheMessage { get; set; } - public string Secret { get; set; } + public string Secret { get; set; } - public string SubPropertySecret { get; set; } + public string SubPropertySecret { get; set; } - public List CreditCards { get; set; } - } + public List CreditCards { get; set; } + } - public class Endpoint : EndpointConfigurationBuilder + public class Endpoint : EndpointConfigurationBuilder + { + public Endpoint() { - public Endpoint() - { - var keys = new Dictionary - { - {"1st", Encoding.ASCII.GetBytes("gdDbqRpqdRbTs3mhdZh9qCaDaxJXl+e6")} - }; + var keys = new Dictionary { { "1st", "gdDbqRpqdRbTs3mhdZh9qCaDaxJXl+e6"u8.ToArray() } }; - EndpointSetup(builder => builder.EnableMessagePropertyEncryption(new AesEncryptionService("1st", keys))); - } + EndpointSetup(builder => builder.EnableMessagePropertyEncryption(new AesEncryptionService("1st", keys))); + } - public class Handler : IHandleMessages + public class Handler(Context testContext) : IHandleMessages + { + public Task Handle(MessageWithSecretData message, IMessageHandlerContext context) { - Context testContext; + testContext.Secret = message.Secret.Value; - public Handler(Context testContext) - { - this.testContext = testContext; - } - - public Task Handle(MessageWithSecretData message, IMessageHandlerContext context) - { - testContext.Secret = message.Secret.Value; - - testContext.SubPropertySecret = message.SubProperty.Secret.Value; + testContext.SubPropertySecret = message.SubProperty.Secret.Value; - testContext.CreditCards = - [ - message.CreditCards[0].Number.Value, - message.CreditCards[1].Number.Value - ]; + testContext.CreditCards = + [ + message.CreditCards[0].Number.Value, + message.CreditCards[1].Number.Value + ]; - testContext.GetTheMessage = true; + testContext.GetTheMessage = true; - return Task.FromResult(0); - } + return Task.FromResult(0); } } + } - public class MessageWithSecretData : IMessage - { - public EncryptedString Secret { get; set; } - public MySecretSubProperty SubProperty { get; set; } - public List CreditCards { get; set; } - } + public class MessageWithSecretData : IMessage + { + public EncryptedString Secret { get; set; } + public MySecretSubProperty SubProperty { get; set; } + public List CreditCards { get; set; } + } - public class CreditCardDetails - { - public DateTime ValidTo { get; set; } - public EncryptedString Number { get; set; } - } + public class CreditCardDetails + { + public DateTime ValidTo { get; set; } + public EncryptedString Number { get; set; } + } - public class MySecretSubProperty - { - public EncryptedString Secret { get; set; } - } + public class MySecretSubProperty + { + public EncryptedString Secret { get; set; } } } \ No newline at end of file diff --git a/src/AcceptanceTests/When_using_Aes_with_unobtrusive_mode.cs b/src/AcceptanceTests/When_using_Aes_with_unobtrusive_mode.cs index c31d64e0..7df7c3bd 100644 --- a/src/AcceptanceTests/When_using_Aes_with_unobtrusive_mode.cs +++ b/src/AcceptanceTests/When_using_Aes_with_unobtrusive_mode.cs @@ -1,144 +1,127 @@ -namespace NServiceBus.Encryption.MessageProperty.AcceptanceTests +namespace NServiceBus.Encryption.MessageProperty.AcceptanceTests; + +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using AcceptanceTesting; +using AcceptanceTesting.Customization; +using NUnit.Framework; + +public class When_using_Aes_with_unobtrusive_mode : NServiceBusAcceptanceTest { - using System; - using System.Collections.Generic; - using System.Linq; - using System.Text; - using System.Threading.Tasks; - using AcceptanceTesting; - using AcceptanceTesting.Customization; - using NUnit.Framework; - - public class When_using_Aes_with_unobtrusive_mode : NServiceBusAcceptanceTest + [Test] + public async Task Should_receive_decrypted_message() { - [Test] - public async Task Should_receive_decrypted_message() + var messageToSend = new MessageWithSecretData { - var context = await Scenario.Define() - .WithEndpoint(b => b.When(session => session.Send(new MessageWithSecretData + EncryptedSecret = "betcha can't guess my secret", + SubProperty = new MySecretSubProperty { EncryptedSecret = "My sub secret" }, + CreditCards = + [ + new CreditCardDetails + { + ValidTo = DateTime.UtcNow.AddYears(1), + EncryptedNumber = "312312312312312" + }, + new CreditCardDetails { - EncryptedSecret = "betcha can't guess my secret", - SubProperty = new MySecretSubProperty - { - EncryptedSecret = "My sub secret" - }, - CreditCards = - [ - new CreditCardDetails - { - ValidTo = DateTime.UtcNow.AddYears(1), - EncryptedNumber = "312312312312312" - }, - new CreditCardDetails - { - ValidTo = DateTime.UtcNow.AddYears(2), - EncryptedNumber = "543645546546456" - } - ] - }))) - .WithEndpoint() - .Done(c => c.GetTheMessage || c.FailedMessages.Any()) - .Run(); - - Assert.AreEqual("betcha can't guess my secret", context.Secret); - Assert.AreEqual("My sub secret", context.SubPropertySecret); - CollectionAssert.AreEquivalent(new List + ValidTo = DateTime.UtcNow.AddYears(2), + EncryptedNumber = "543645546546456" + } + ] + }; + + var context = await Scenario.Define() + .WithEndpoint(b => b.When(session => session.Send(messageToSend))) + .WithEndpoint() + .Done(c => c.Secret != null) + .Run(); + + Assert.Multiple(() => + { + Assert.That(context.Secret, Is.EqualTo(messageToSend.EncryptedSecret)); + Assert.That(context.SubPropertySecret, Is.EqualTo(messageToSend.SubProperty.EncryptedSecret)); + Assert.That(context.CreditCards, Is.EquivalentTo(new List { "312312312312312", "543645546546456" - }, context.CreditCards); - } + })); + }); + } - static Dictionary Keys = new Dictionary - { - {"1st", Encoding.ASCII.GetBytes("gdDbqRpqdRbTs3mhdZh9qCaDaxJXl+e6")} - }; + static readonly Dictionary Keys = new() { { "1st", "gdDbqRpqdRbTs3mhdZh9qCaDaxJXl+e6"u8.ToArray() } }; - public class Context : ScenarioContext - { - public bool GetTheMessage { get; set; } + public class Context : ScenarioContext + { + public bool GetTheMessage { get; set; } - public string Secret { get; set; } + public string Secret { get; set; } - public string SubPropertySecret { get; set; } + public string SubPropertySecret { get; set; } - public List CreditCards { get; set; } - } + public List CreditCards { get; set; } + } - public class Sender : EndpointConfigurationBuilder - { - public Sender() + public class Sender : EndpointConfigurationBuilder + { + public Sender() => + EndpointSetup(c => { - EndpointSetup(c => - { - c.Conventions().DefiningCommandsAs(t => t.Namespace != null && t.FullName == typeof(MessageWithSecretData).FullName); + c.Conventions().DefiningCommandsAs(t => t.Namespace != null && t.FullName == typeof(MessageWithSecretData).FullName); - c.EnableMessagePropertyEncryption(new AesEncryptionService("1st", Keys), t => t.Name.StartsWith("Encrypted")); + c.EnableMessagePropertyEncryption(new AesEncryptionService("1st", Keys), t => t.Name.StartsWith("Encrypted")); - c.ConfigureRouting() - .RouteToEndpoint(typeof(MessageWithSecretData), Conventions.EndpointNamingConvention(typeof(Receiver))); - }) - // remove that type from assembly scanning to simulate what would happen with true unobtrusive mode - .ExcludeType(); - } - } + c.ConfigureRouting() + .RouteToEndpoint(typeof(MessageWithSecretData), Conventions.EndpointNamingConvention(typeof(Receiver))); + }); + } - public class Receiver : EndpointConfigurationBuilder - { - public Receiver() + public class Receiver : EndpointConfigurationBuilder + { + public Receiver() => + EndpointSetup(c => { - EndpointSetup(c => - { - c.Conventions().DefiningCommandsAs(t => t.Namespace != null && t.FullName == typeof(MessageWithSecretData).FullName); + c.Conventions().DefiningCommandsAs(t => t.Namespace != null && t.FullName == typeof(MessageWithSecretData).FullName); - c.EnableMessagePropertyEncryption(new AesEncryptionService("1st", Keys), t => t.Name.StartsWith("Encrypted")); - }); - } + c.EnableMessagePropertyEncryption(new AesEncryptionService("1st", Keys), t => t.Name.StartsWith("Encrypted")); + }); - public class Handler : IHandleMessages + public class Handler(Context testContext) : IHandleMessages + { + public Task Handle(MessageWithSecretData message, IMessageHandlerContext context) { - Context testContext; + testContext.Secret = message.EncryptedSecret; - public Handler(Context testContext) - { - this.testContext = testContext; - } + testContext.SubPropertySecret = message.SubProperty.EncryptedSecret; - public Task Handle(MessageWithSecretData message, IMessageHandlerContext context) - { - testContext.Secret = message.EncryptedSecret; - - testContext.SubPropertySecret = message.SubProperty.EncryptedSecret; - - testContext.CreditCards = - [ - message.CreditCards[0].EncryptedNumber, - message.CreditCards[1].EncryptedNumber - ]; + testContext.CreditCards = + [ + message.CreditCards[0].EncryptedNumber, + message.CreditCards[1].EncryptedNumber + ]; - testContext.GetTheMessage = true; + testContext.GetTheMessage = true; - return Task.FromResult(0); - } + return Task.CompletedTask; } } + } - public class MessageWithSecretData - { - public string EncryptedSecret { get; set; } - public MySecretSubProperty SubProperty { get; set; } - public List CreditCards { get; set; } - } + public class MessageWithSecretData + { + public string EncryptedSecret { get; set; } + public MySecretSubProperty SubProperty { get; set; } + public List CreditCards { get; set; } + } - public class CreditCardDetails - { - public DateTime ValidTo { get; set; } - public string EncryptedNumber { get; set; } - } + public class CreditCardDetails + { + public DateTime ValidTo { get; set; } + public string EncryptedNumber { get; set; } + } - public class MySecretSubProperty - { - public string EncryptedSecret { get; set; } - } + public class MySecretSubProperty + { + public string EncryptedSecret { get; set; } } } \ No newline at end of file diff --git a/src/MessageProperty/DecryptBehavior.cs b/src/MessageProperty/DecryptBehavior.cs index 4f851eee..56f6f5f0 100644 --- a/src/MessageProperty/DecryptBehavior.cs +++ b/src/MessageProperty/DecryptBehavior.cs @@ -17,9 +17,9 @@ public Task Invoke(IIncomingLogicalMessageContext context, Func + class EncryptBehavior(EncryptionInspector messageInspector, IEncryptionService encryptionService) : IBehavior { - public EncryptBehavior(EncryptionInspector messageInspector, IEncryptionService encryptionService) - { - this.messageInspector = messageInspector; - this.encryptionService = encryptionService; - } - - public Task Invoke(IOutgoingLogicalMessageContext context, Func next) + public async Task Invoke(IOutgoingLogicalMessageContext context, Func next) { var currentMessageToSend = context.Message.Instance; - foreach (var item in messageInspector.ScanObject(currentMessageToSend)) + var propertiesToRestore = new List<(object unencryptedValue, object target, MemberInfo member)>(); + var propertiesToEncrypt = messageInspector.ScanObject(currentMessageToSend); + + try { - EncryptMember(item.Item1, item.Item2, context); - } + foreach (var (target, member) in propertiesToEncrypt) + { + var oldValue = EncryptMember(target, member, context); + + propertiesToRestore.Add((oldValue, target, member)); + } - context.UpdateMessage(currentMessageToSend); + context.UpdateMessage(currentMessageToSend); - return next(context); + await next(context).ConfigureAwait(false); + } + finally + { + if (propertiesToEncrypt.Any()) + { + foreach (var propertyToRestore in propertiesToRestore) + { + propertyToRestore.member.SetValue(propertyToRestore.target, propertyToRestore.unencryptedValue); + } + + context.UpdateMessage(currentMessageToSend); + } + } } - void EncryptMember(object message, MemberInfo member, IOutgoingLogicalMessageContext context) + object EncryptMember(object message, MemberInfo member, IOutgoingLogicalMessageContext context) { var valueToEncrypt = member.GetValue(message); if (valueToEncrypt is EncryptedString wireEncryptedString) { + var unencryptedValue = new EncryptedString { Value = wireEncryptedString.Value }; encryptionService.EncryptValue(wireEncryptedString, context); - return; + return unencryptedValue; } if (valueToEncrypt is string stringToEncrypt) { + var unencryptedValue = stringToEncrypt; encryptionService.EncryptValue(ref stringToEncrypt, context); member.SetValue(message, stringToEncrypt); - return; + return unencryptedValue; } throw new Exception("Only string properties are supported for convention based encryption. Check the configured conventions."); } - IEncryptionService encryptionService; - EncryptionInspector messageInspector; - public class EncryptRegistration : RegisterStep { public EncryptRegistration(EncryptionInspector inspector, IEncryptionService encryptionService) - : base("MessagePropertyEncryption", typeof(EncryptBehavior), "Invokes the encryption logic", b => new EncryptBehavior(inspector, encryptionService)) - { + : base("MessagePropertyEncryption", typeof(EncryptBehavior), "Invokes the encryption logic", b => new EncryptBehavior(inspector, encryptionService)) => InsertAfter("MutateOutgoingMessages"); - } } } } \ No newline at end of file diff --git a/src/MessageProperty/EncryptionInspector.cs b/src/MessageProperty/EncryptionInspector.cs index 70937c0d..ba5ebd6b 100644 --- a/src/MessageProperty/EncryptionInspector.cs +++ b/src/MessageProperty/EncryptionInspector.cs @@ -47,7 +47,7 @@ bool IsEncryptedMember(MemberInfo arg) return false; } - public List> ScanObject(object root) + public List<(object target, MemberInfo member)> ScanObject(object root) { #pragma warning disable PS0025 // Dictionary keys should implement IEquatable - Valid use for object counting var visitedMembers = new HashSet(); @@ -56,7 +56,7 @@ public List> ScanObject(object root) } #pragma warning disable PS0025 // Dictionary keys should implement IEquatable - Valid use for object counting - List> ScanObject(object root, HashSet visitedMembers) + List<(object target, MemberInfo member)> ScanObject(object root, HashSet visitedMembers) #pragma warning restore PS0025 // Dictionary keys should implement IEquatable { if (root == null || visitedMembers.Contains(root)) @@ -68,7 +68,7 @@ List> ScanObject(object root, HashSet visitedM var members = GetFieldsAndProperties(root); - var properties = new List>(); + var properties = new List<(object target, MemberInfo member)>(); foreach (var member in members) { @@ -77,7 +77,7 @@ List> ScanObject(object root, HashSet visitedM var value = member.GetValue(root); if (value is string or EncryptedString) { - properties.Add(Tuple.Create(root, member)); + properties.Add((root, member)); continue; } throw new Exception("Only string properties are supported for convention based encryption. Check the configured conventions."); @@ -161,7 +161,7 @@ static List GetFieldsAndProperties(object target) static List NoMembers = []; - static List> AlreadyVisited = []; + static List<(object target, MemberInfo member)> AlreadyVisited = []; static ConcurrentDictionary> cache = new ConcurrentDictionary>(); } diff --git a/src/Tests/ConventionBasedEncryptedStringSpecs.cs b/src/Tests/ConventionBasedEncryptedStringSpecs.cs index 9dc5cee0..164fb25d 100644 --- a/src/Tests/ConventionBasedEncryptedStringSpecs.cs +++ b/src/Tests/ConventionBasedEncryptedStringSpecs.cs @@ -18,8 +18,8 @@ public void Should_return_the_value() var result = inspector.ScanObject(message).ToList(); - Assert.AreEqual(1, result.Count); - Assert.AreEqual("EncryptedSecret", result[0].Item2.Name); + Assert.That(result, Has.Count.EqualTo(1)); + Assert.That(result[0].member.Name, Is.EqualTo("EncryptedSecret")); } } diff --git a/src/Tests/WireEncryptedStringSpecs.cs b/src/Tests/WireEncryptedStringSpecs.cs index 0fc8bdea..775156b6 100644 --- a/src/Tests/WireEncryptedStringSpecs.cs +++ b/src/Tests/WireEncryptedStringSpecs.cs @@ -27,18 +27,21 @@ public void Should_use_the_wireEncrypted_string() message.ListOfSecrets = [.. message.ListOfCreditCards]; var result = inspector.ScanObject(message).ToList(); - result.ForEach(x => x.Item2.SetValue(x.Item1, Create())); + result.ForEach(x => x.member.SetValue(x.target, Create())); - Assert.AreEqual(5, result.Count); + Assert.Multiple(() => + { + Assert.That(result, Has.Count.EqualTo(5)); - Assert.AreEqual(EncryptedBase64Value, message.Secret.EncryptedValue.EncryptedBase64Value); - Assert.AreEqual(EncryptedBase64Value, message.SecretField.EncryptedValue.EncryptedBase64Value); - Assert.AreEqual(EncryptedBase64Value, message.CreditCard.CreditCardNumber.EncryptedValue.EncryptedBase64Value); - Assert.AreEqual(EncryptedBase64Value, message.ListOfCreditCards[0].CreditCardNumber.EncryptedValue.EncryptedBase64Value); - Assert.AreEqual(EncryptedBase64Value, message.ListOfCreditCards[1].CreditCardNumber.EncryptedValue.EncryptedBase64Value); + Assert.That(message.Secret.EncryptedValue.EncryptedBase64Value, Is.EqualTo(EncryptedBase64Value)); + Assert.That(message.SecretField.EncryptedValue.EncryptedBase64Value, Is.EqualTo(EncryptedBase64Value)); + Assert.That(message.CreditCard.CreditCardNumber.EncryptedValue.EncryptedBase64Value, Is.EqualTo(EncryptedBase64Value)); + Assert.That(message.ListOfCreditCards[0].CreditCardNumber.EncryptedValue.EncryptedBase64Value, Is.EqualTo(EncryptedBase64Value)); + Assert.That(message.ListOfCreditCards[1].CreditCardNumber.EncryptedValue.EncryptedBase64Value, Is.EqualTo(EncryptedBase64Value)); - Assert.AreEqual(EncryptedBase64Value, message.ListOfSecrets[0].CreditCardNumber.EncryptedValue.EncryptedBase64Value); - Assert.AreEqual(EncryptedBase64Value, message.ListOfSecrets[1].CreditCardNumber.EncryptedValue.EncryptedBase64Value); + Assert.That(message.ListOfSecrets[0].CreditCardNumber.EncryptedValue.EncryptedBase64Value, Is.EqualTo(EncryptedBase64Value)); + Assert.That(message.ListOfSecrets[1].CreditCardNumber.EncryptedValue.EncryptedBase64Value, Is.EqualTo(EncryptedBase64Value)); + }); } } @@ -58,8 +61,8 @@ public void Should_match_the_property_correctly() var result = inspector.ScanObject(message).ToList(); - Assert.AreEqual(1, result.Count); - Assert.AreSame(message.Secret, result[0].Item2.GetValue(result[0].Item1)); + Assert.That(result, Has.Count.EqualTo(1)); + Assert.That(result[0].member.GetValue(result[0].target), Is.SameAs(message.Secret)); } public class MessageWithIndexedProperties : IMessage @@ -88,7 +91,7 @@ public void Should_throw_exception() message[1] = Create(); var exception = Assert.Throws(() => inspector.ScanObject(message).ToList()); - Assert.AreEqual("Cannot encrypt or decrypt indexed properties that return a WireEncryptedString.", exception.Message); + Assert.That(exception.Message, Is.EqualTo("Cannot encrypt or decrypt indexed properties that return a WireEncryptedString.")); } public class MessageWithIndexedProperties : IMessage @@ -120,8 +123,8 @@ public void Should_match_the_property_correctly() var result = inspector.ScanObject(message).ToList(); - Assert.AreEqual(1, result.Count); - Assert.AreEqual("Secret", result[0].Item2.Name); + Assert.That(result, Has.Count.EqualTo(1)); + Assert.That(result[0].member.Name, Is.EqualTo("Secret")); } } @@ -140,9 +143,9 @@ public void Should_match_the_property_correctly() inspector .ScanObject(message) .ToList() - .ForEach(x => x.Item2.SetValue(x.Item1, (EncryptedString)MySecretMessage)); + .ForEach(x => x.member.SetValue(x.target, (EncryptedString)MySecretMessage)); - Assert.AreEqual(MySecretMessage, message.MySecret.Value); + Assert.That(message.MySecret.Value, Is.EqualTo(MySecretMessage)); } public class MessageWithPropertyWithBackingPublicField : IMessage @@ -169,9 +172,9 @@ public void Should_decrypt_correctly() inspector .ScanObject(message) .ToList() - .ForEach(x => x.Item2.SetValue(x.Item1, Create())); + .ForEach(x => x.member.SetValue(x.target, Create())); - Assert.AreEqual(message.Secret.Value, MySecretMessage); + Assert.That(MySecretMessage, Is.EqualTo(message.Secret.Value)); } } @@ -192,15 +195,18 @@ public void Should_use_the_wireEncrypted_string() inspector .ScanObject(message) .ToList() - .ForEach(x => x.Item2.SetValue(x.Item1, Create())); + .ForEach(x => x.member.SetValue(x.target, Create())); - Assert.AreEqual(MySecretMessage, message.Secret.Value); - Assert.AreEqual(MySecretMessage, message.SecretField.Value); - Assert.AreEqual(MySecretMessage, message.CreditCard.CreditCardNumber.Value); + Assert.Multiple(() => + { + Assert.That(message.Secret.Value, Is.EqualTo(MySecretMessage)); + Assert.That(message.SecretField.Value, Is.EqualTo(MySecretMessage)); + Assert.That(message.CreditCard.CreditCardNumber.Value, Is.EqualTo(MySecretMessage)); + }); } } - public class WireEncryptedStringContext + public abstract class WireEncryptedStringContext { internal EncryptionInspector inspector;