diff --git a/src/Testing/CoreTests/Transports/MapIncomingPropertyTests.cs b/src/Testing/CoreTests/Transports/MapIncomingPropertyTests.cs index 8c282672c..df979d063 100644 --- a/src/Testing/CoreTests/Transports/MapIncomingPropertyTests.cs +++ b/src/Testing/CoreTests/Transports/MapIncomingPropertyTests.cs @@ -1,3 +1,4 @@ +using Shouldly; using Wolverine.Configuration; using Wolverine.Runtime; using Wolverine.Transports; @@ -26,6 +27,111 @@ public void uses_the_custom_incoming_mapping() envelope.ReplyUri.ShouldBe(expectedReplyUri); } + + // Regression coverage for https://github.com/JasperFx/wolverine/issues/2551. + // + // MapIncomingProperty / MapOutgoingProperty customize a single direction. + // They must NOT disturb the opposite direction's default header mapping + // (registered via MapPropertyToHeader during the mapper's constructor). + // + // The bug was that compileIncoming/compileOutgoing had their filter + // predicates swapped — so MapIncomingProperty silently deleted the + // outgoing header mapping and vice versa. + + [Fact] + public void custom_incoming_property_preserves_default_outgoing_header() + { + var mapper = new StubEnvelopeMapper(new StubEndpoint()); + + // Customize only the incoming direction for Id — read it from a + // transport-specific location. This must not stop Id from being + // written as the "id" header on outgoing messages. + mapper.MapIncomingProperty(x => x.Id, (envelope, incoming) => + { + if (incoming.Headers.TryGetValue("custom-id", out var raw) && Guid.TryParse(raw, out var parsed)) + { + envelope.Id = parsed; + } + }); + + var envelope = new Envelope { Id = Guid.NewGuid() }; + var outgoing = new StubTransportMessage(); + + mapper.MapEnvelopeToOutgoing(envelope, outgoing); + + outgoing.Headers.ContainsKey(EnvelopeConstants.IdKey).ShouldBeTrue( + "MapIncomingProperty(x => x.Id, ...) must not delete the default outgoing 'id' header mapping"); + outgoing.Headers[EnvelopeConstants.IdKey].ShouldBe(envelope.Id.ToString()); + } + + [Fact] + public void custom_outgoing_property_preserves_default_incoming_header_read() + { + var mapper = new StubEnvelopeMapper(new StubEndpoint()); + + // Customize only the outgoing direction for Id — write it to a + // transport-specific location. This must not stop Id from being + // read from the default "id" header on incoming messages. + mapper.MapOutgoingProperty(x => x.Id, (envelope, outgoing) => + { + outgoing.Headers["custom-id"] = envelope.Id.ToString(); + }); + + var expected = Guid.NewGuid(); + var envelope = new Envelope(); + mapper.MapIncomingToEnvelope(envelope, new StubTransportMessage + { + Headers = { [EnvelopeConstants.IdKey] = expected.ToString() } + }); + + envelope.Id.ShouldBe(expected); + } + + [Fact] + public void custom_incoming_property_still_overrides_its_own_direction() + { + // Sanity check: the customization itself still works on the direction + // it actually targets. This complements + // custom_incoming_property_preserves_default_outgoing_header so a future + // regression that over-corrects can't slip past. + var mapper = new StubEnvelopeMapper(new StubEndpoint()); + + mapper.MapIncomingProperty(x => x.Id, (envelope, incoming) => + { + if (incoming.Headers.TryGetValue("custom-id", out var raw) && Guid.TryParse(raw, out var parsed)) + { + envelope.Id = parsed; + } + }); + + var expected = Guid.NewGuid(); + var envelope = new Envelope(); + mapper.MapIncomingToEnvelope(envelope, new StubTransportMessage + { + Headers = { ["custom-id"] = expected.ToString() } + }); + + envelope.Id.ShouldBe(expected); + } + + [Fact] + public void custom_outgoing_property_still_overrides_its_own_direction() + { + var mapper = new StubEnvelopeMapper(new StubEndpoint()); + + mapper.MapOutgoingProperty(x => x.Id, (envelope, outgoing) => + { + outgoing.Headers["custom-id"] = envelope.Id.ToString(); + }); + + var envelope = new Envelope { Id = Guid.NewGuid() }; + var outgoing = new StubTransportMessage(); + + mapper.MapEnvelopeToOutgoing(envelope, outgoing); + + outgoing.Headers.ContainsKey("custom-id").ShouldBeTrue(); + outgoing.Headers["custom-id"].ShouldBe(envelope.Id.ToString()); + } } internal class StubTransportMessage diff --git a/src/Wolverine/Transports/EnvelopeMapper.cs b/src/Wolverine/Transports/EnvelopeMapper.cs index ecce8980c..1b64d4895 100644 --- a/src/Wolverine/Transports/EnvelopeMapper.cs +++ b/src/Wolverine/Transports/EnvelopeMapper.cs @@ -195,7 +195,12 @@ private Action compileIncoming() writeHeaders }; - foreach (var pair in _envelopeToHeader.Where(x => !_envelopeToOutgoing.ContainsKey(x.Key))) + // Use the default header read for a property unless the caller has + // supplied a custom incoming mapping for it. Previously this predicate + // was accidentally checking _envelopeToOutgoing, which caused + // MapOutgoingProperty to silently delete the incoming header read for + // the same property. See https://github.com/JasperFx/wolverine/issues/2551. + foreach (var pair in _envelopeToHeader.Where(x => !_incomingToEnvelope.ContainsKey(x.Key))) { var getMethod = getString!; if (pair.Key.PropertyType == typeof(Uri)) @@ -278,7 +283,12 @@ private Action compileOutgoing() writeHeaders }; - var headers = _envelopeToHeader.Where(x => !_incomingToEnvelope.ContainsKey(x.Key)); + // Use the default header write for a property unless the caller has + // supplied a custom outgoing mapping for it. Previously this predicate + // was accidentally checking _incomingToEnvelope, which caused + // MapIncomingProperty to silently delete the outgoing header write for + // the same property. See https://github.com/JasperFx/wolverine/issues/2551. + var headers = _envelopeToHeader.Where(x => !_envelopeToOutgoing.ContainsKey(x.Key)); foreach (var pair in headers) { var setMethod = setString!;