Skip to content

Commit f8b1fe0

Browse files
committed
fix: unwrap original exceptions in PipelineBuilder and QueryProcessor
- Updated exception handling in PipelineBuilder to unwrap the original exception instead of wrapping it in a FormatException. - Enhanced QueryProcessor to preserve the original exception when a TargetInvocationException occurs. - Added comprehensive tests to ensure original exceptions are correctly thrown and handled in various scenarios.
1 parent 61137a1 commit f8b1fe0

File tree

3 files changed

+229
-2
lines changed

3 files changed

+229
-2
lines changed

src/Paramore.Darker/PipelineBuilder.cs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,8 @@ public Func<IQuery<TResult>, TResult> Build(IQuery<TResult> query, IQueryContext
5555
}
5656
catch (TargetInvocationException targetInvocationException)
5757
{
58-
throw new FormatException("One of the identified items was in an invalid format", targetInvocationException);
58+
// Unwrap the original exception instead of wrapping it in a FormatException
59+
throw targetInvocationException.InnerException;
5960
}
6061
}
6162
};
@@ -98,7 +99,8 @@ public Func<IQuery<TResult>, CancellationToken, Task<TResult>> BuildAsync(IQuery
9899
}
99100
catch (TargetInvocationException targetInvocationException)
100101
{
101-
throw new FormatException("One of the identified items was in an invalid format", targetInvocationException);
102+
// Unwrap the original exception instead of wrapping it in a FormatException
103+
throw targetInvocationException.InnerException;
102104
}
103105
}
104106
};

src/Paramore.Darker/QueryProcessor.cs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
using System;
22
using System.Collections.Generic;
33
using System.Linq;
4+
using System.Reflection;
45
using System.Threading;
56
using System.Threading.Tasks;
67
using Microsoft.Extensions.Logging;
@@ -45,6 +46,10 @@ public TResult Execute<TResult>(IQuery<TResult> query)
4546
{
4647
return entryPoint.Invoke(query);
4748
}
49+
catch (TargetInvocationException targetInvocationException)
50+
{
51+
throw targetInvocationException.InnerException;
52+
}
4853
catch (Exception ex)
4954
{
5055
_logger.LogInformation(ex,"An exception was thrown during pipeline execution");
@@ -65,6 +70,10 @@ public TResult Execute<TResult>(IQuery<TResult> query)
6570
_logger.LogDebug("Invoking async pipeline...");
6671
return await entryPoint.Invoke(query, cancellationToken).ConfigureAwait(false);
6772
}
73+
catch (TargetInvocationException targetInvocationException)
74+
{
75+
throw targetInvocationException.InnerException;
76+
}
6877
catch (Exception ex)
6978
{
7079
_logger.LogInformation(ex,"An exception was thrown during async pipeline execution");
Lines changed: 216 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,216 @@
1+
using System;
2+
using System.Linq;
3+
using System.Threading;
4+
using System.Threading.Tasks;
5+
using System.Reflection;
6+
using Moq;
7+
using Shouldly;
8+
using Xunit;
9+
using Paramore.Darker.Attributes;
10+
using Paramore.Darker.Decorators;
11+
12+
namespace Paramore.Darker.Tests
13+
{
14+
public class PipelineBuilderExceptionTests
15+
{
16+
private readonly Mock<IQueryHandlerFactory> _handlerFactory;
17+
private readonly IQueryHandlerRegistry _handlerRegistry;
18+
private readonly IQueryProcessor _queryProcessor;
19+
private readonly Mock<IQueryHandlerDecoratorFactory> _decoratorFactory;
20+
21+
// Query and handler for normal exception scenario
22+
private class ExceptionQuery : IQuery<ExceptionQuery.Result>
23+
{
24+
public class Result { }
25+
}
26+
private class ExceptionQueryHandler : QueryHandler<ExceptionQuery, ExceptionQuery.Result>
27+
{
28+
public override ExceptionQuery.Result Execute(ExceptionQuery query)
29+
{
30+
throw new InvalidOperationException("Test exception from Execute");
31+
}
32+
public override Task<ExceptionQuery.Result> ExecuteAsync(ExceptionQuery query, CancellationToken cancellationToken = default)
33+
{
34+
throw new ArgumentException("Test exception from ExecuteAsync");
35+
}
36+
}
37+
38+
// Query and handler for fallback exception scenario
39+
private class FallbackExceptionQuery : IQuery<FallbackExceptionQuery.Result>
40+
{
41+
public class Result { }
42+
}
43+
private class FallbackExceptionQueryHandler : QueryHandler<FallbackExceptionQuery, FallbackExceptionQuery.Result>
44+
{
45+
[FallbackPolicy(step: 1, typeof(InvalidOperationException))]
46+
public override FallbackExceptionQuery.Result Execute(FallbackExceptionQuery query)
47+
{
48+
throw new InvalidOperationException("Test exception from Execute");
49+
}
50+
public override FallbackExceptionQuery.Result Fallback(FallbackExceptionQuery query)
51+
{
52+
throw new NotSupportedException("Test exception from Fallback");
53+
}
54+
}
55+
56+
// Query and handler for null inner exception scenario
57+
private class NullInnerExceptionQuery : IQuery<string> { }
58+
private class NullInnerExceptionQueryHandler : QueryHandler<NullInnerExceptionQuery, string>
59+
{
60+
public override string Execute(NullInnerExceptionQuery query)
61+
{
62+
throw new TargetInvocationException(null);
63+
}
64+
}
65+
66+
// Query and handler for decorator exception scenario
67+
private class DecoratorExceptionQuery : IQuery<DecoratorExceptionQuery.Result>
68+
{
69+
public class Result { }
70+
}
71+
private class DecoratorExceptionQueryHandler : QueryHandler<DecoratorExceptionQuery, DecoratorExceptionQuery.Result>
72+
{
73+
[DecoratorException(step: 1)]
74+
public override DecoratorExceptionQuery.Result Execute(DecoratorExceptionQuery query)
75+
{
76+
return new DecoratorExceptionQuery.Result();
77+
}
78+
}
79+
private class TestExceptionDecorator<TQuery, TResult> : IQueryHandlerDecorator<TQuery, TResult>
80+
where TQuery : IQuery<TResult>
81+
{
82+
public IQueryContext Context { get; set; }
83+
public void InitializeFromAttributeParams(object[] attributeParams) { }
84+
public TResult Execute(TQuery query, Func<TQuery, TResult> next, Func<TQuery, TResult> fallback)
85+
{
86+
throw new InvalidOperationException("Test exception from decorator");
87+
}
88+
89+
public Task<TResult> ExecuteAsync(TQuery query, Func<TQuery, CancellationToken, Task<TResult>> next, Func<TQuery, CancellationToken, Task<TResult>> fallback,
90+
CancellationToken cancellationToken = default(CancellationToken))
91+
{
92+
throw new InvalidOperationException("Test exception from async decorator");
93+
}
94+
95+
}
96+
97+
98+
[AttributeUsage(AttributeTargets.Method)]
99+
public sealed class DecoratorExceptionAttribute : QueryHandlerAttribute
100+
{
101+
102+
public DecoratorExceptionAttribute(int step) : base(step)
103+
{
104+
}
105+
106+
public override object[] GetAttributeParams()
107+
{
108+
return [];
109+
}
110+
111+
public override Type GetDecoratorType()
112+
{
113+
return typeof(TestExceptionDecorator<,>);
114+
}
115+
}
116+
117+
public PipelineBuilderExceptionTests()
118+
{
119+
_handlerRegistry = new QueryHandlerRegistry();
120+
_handlerFactory = new Mock<IQueryHandlerFactory>();
121+
_decoratorFactory = new Mock<IQueryHandlerDecoratorFactory>();
122+
var decoratorRegistry = new Mock<IQueryHandlerDecoratorRegistry>();
123+
124+
// Register the decorators
125+
decoratorRegistry.Setup(x => x.Register(typeof(FallbackPolicyDecorator<,>)));
126+
decoratorRegistry.Setup(x => x.Register(typeof(TestExceptionDecorator<,>)));
127+
128+
var handlerConfiguration = new HandlerConfiguration(
129+
_handlerRegistry,
130+
_handlerFactory.Object,
131+
decoratorRegistry.Object,
132+
_decoratorFactory.Object);
133+
134+
_queryProcessor = new QueryProcessor(handlerConfiguration, new InMemoryQueryContextFactory());
135+
}
136+
137+
[Fact]
138+
public void ShouldPreserveOriginalExceptionWhenHandlerThrowsException()
139+
{
140+
// Arrange
141+
_handlerRegistry.Register<ExceptionQuery, ExceptionQuery.Result, ExceptionQueryHandler>();
142+
_handlerFactory.Setup(x => x.Create(typeof(ExceptionQueryHandler))).Returns(new ExceptionQueryHandler());
143+
var query = new ExceptionQuery();
144+
145+
// Act & Assert
146+
var exception = Should.Throw<InvalidOperationException>(() => _queryProcessor.Execute(query));
147+
exception.Message.ShouldBe("Test exception from Execute");
148+
_handlerFactory.Verify(x => x.Release(It.IsAny<ExceptionQueryHandler>()), Times.Once);
149+
}
150+
151+
[Fact]
152+
public async Task ShouldPreserveOriginalExceptionWhenHandlerThrowsExceptionAsync()
153+
{
154+
// Arrange
155+
_handlerRegistry.Register<ExceptionQuery, ExceptionQuery.Result, ExceptionQueryHandler>();
156+
_handlerFactory.Setup(x => x.Create(typeof(ExceptionQueryHandler))).Returns(new ExceptionQueryHandler());
157+
var query = new ExceptionQuery();
158+
159+
// Act & Assert
160+
var exception = await Should.ThrowAsync<ArgumentException>(async () =>
161+
await _queryProcessor.ExecuteAsync(query));
162+
exception.Message.ShouldBe("Test exception from ExecuteAsync");
163+
_handlerFactory.Verify(x => x.Release(It.IsAny<ExceptionQueryHandler>()), Times.Once);
164+
}
165+
166+
[Fact]
167+
public void ShouldThrowNullReferenceExceptionWhenInnerExceptionIsNull()
168+
{
169+
// Arrange
170+
_handlerRegistry.Register<NullInnerExceptionQuery, string, NullInnerExceptionQueryHandler>();
171+
_handlerFactory.Setup(x => x.Create(typeof(NullInnerExceptionQueryHandler))).Returns(new NullInnerExceptionQueryHandler());
172+
var query = new NullInnerExceptionQuery();
173+
174+
// Act & Assert
175+
var exception = Should.Throw<NullReferenceException>(() => _queryProcessor.Execute(query));
176+
exception.InnerException.ShouldBeNull();
177+
}
178+
179+
[Fact]
180+
public void ShouldThrowExceptionWhenFallbackThrowsException()
181+
{
182+
183+
// Arrange
184+
var decorator = new FallbackPolicyDecorator<IQuery<FallbackExceptionQuery.Result>, FallbackExceptionQuery.Result>();
185+
_handlerRegistry.Register<FallbackExceptionQuery, FallbackExceptionQuery.Result, FallbackExceptionQueryHandler>();
186+
_handlerFactory.Setup(x => x.Create(typeof(FallbackExceptionQueryHandler))).Returns(new FallbackExceptionQueryHandler());
187+
var decoratorType = typeof(FallbackPolicyDecorator<IQuery<FallbackExceptionQuery.Result>, FallbackExceptionQuery.Result>);
188+
_decoratorFactory.Setup(x =>
189+
x.Create<IQueryHandlerDecorator<IQuery<FallbackExceptionQuery.Result>, FallbackExceptionQuery.Result>>(
190+
decoratorType)).Returns(decorator);
191+
var query = new FallbackExceptionQuery();
192+
193+
// Act & Assert
194+
var exception = Should.Throw<NotSupportedException>(() => _queryProcessor.Execute(query));
195+
exception.Message.ShouldBe("Test exception from Fallback");
196+
}
197+
198+
[Fact]
199+
public void ShouldThrowExceptionWhenDecoratorThrowsException()
200+
{
201+
var decorator = new TestExceptionDecorator<IQuery<DecoratorExceptionQuery.Result>, DecoratorExceptionQuery.Result>();
202+
_handlerRegistry.Register<DecoratorExceptionQuery, DecoratorExceptionQuery.Result, DecoratorExceptionQueryHandler>();
203+
_handlerFactory.Setup(x => x.Create(typeof(DecoratorExceptionQueryHandler))).Returns(new DecoratorExceptionQueryHandler());
204+
var decoratorType = typeof(TestExceptionDecorator<IQuery<DecoratorExceptionQuery.Result>, DecoratorExceptionQuery.Result>);
205+
_decoratorFactory.Setup(x =>
206+
x.Create<IQueryHandlerDecorator<IQuery<DecoratorExceptionQuery.Result>, DecoratorExceptionQuery.Result>>(
207+
decoratorType)).Returns(decorator);
208+
var query = new DecoratorExceptionQuery();
209+
210+
211+
// Act & Assert
212+
var exception = Should.Throw<InvalidOperationException>(() => _queryProcessor.Execute(query));
213+
exception.Message.ShouldBe("Test exception from decorator");
214+
}
215+
}
216+
}

0 commit comments

Comments
 (0)