Skip to content

Commit 835eae0

Browse files
Merge pull request #26 from TechBuddyTR/dev
ExceptionHandling Added
2 parents a92eb08 + 0835a2a commit 835eae0

File tree

18 files changed

+823
-3
lines changed

18 files changed

+823
-3
lines changed

TechBuddy.Extensions.sln

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,11 +22,11 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "github_workflows", "github_
2222
ProjectSection(SolutionItems) = preProject
2323
.github\workflows\apiversioning_rb_deploy.yml = .github\workflows\apiversioning_rb_deploy.yml
2424
.github\workflows\cb.yml = .github\workflows\cb.yml
25-
.github\workflows\rb.yml = .github\workflows\rb.yml
2625
.github\workflows\exceptionhandling_rb_deploy.yml = .github\workflows\exceptionhandling_rb_deploy.yml
2726
.github\workflows\openapi_rb_deploy.yml = .github\workflows\openapi_rb_deploy.yml
28-
.github\workflows\vb.yml = .github\workflows\vb.yml
27+
.github\workflows\rb.yml = .github\workflows\rb.yml
2928
.github\workflows\validation_rb_deploy.yml = .github\workflows\validation_rb_deploy.yml
29+
.github\workflows\vb.yml = .github\workflows\vb.yml
3030
EndProjectSection
3131
EndProject
3232
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "ValidationExtension", "ValidationExtension", "{60EF4920-1A12-4E7F-822B-CE069E232A63}"
@@ -39,6 +39,12 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ValidationExtension.Tests.W
3939
EndProject
4040
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ValidationExtension.Tests.AF", "src\ValidationExtension\ValidationExtension.Tests.AF\ValidationExtension.Tests.AF.csproj", "{1BEB1E5C-26D3-42A2-B47A-7BF113822AA6}"
4141
EndProject
42+
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "ExceptionHandlingExtension", "ExceptionHandlingExtension", "{D2AAE295-64F5-4B57-9E13-6D30FF4204FE}"
43+
EndProject
44+
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ExceptionHandlingExtension", "src\ExceptionHandlingExtension\ExceptionHandlingExtension\ExceptionHandlingExtension.csproj", "{A022F7A7-5E60-4DEE-B218-56FCA2464410}"
45+
EndProject
46+
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ExceptionHandlingExtension.Tests", "src\ExceptionHandlingExtension\ExceptionHandlingExtension.Tests\ExceptionHandlingExtension.Tests.csproj", "{A5540922-A24D-461C-BE3B-B8FA85FECFDA}"
47+
EndProject
4248
Global
4349
GlobalSection(SolutionConfigurationPlatforms) = preSolution
4450
Debug|Any CPU = Debug|Any CPU
@@ -73,6 +79,14 @@ Global
7379
{1BEB1E5C-26D3-42A2-B47A-7BF113822AA6}.Debug|Any CPU.Build.0 = Debug|Any CPU
7480
{1BEB1E5C-26D3-42A2-B47A-7BF113822AA6}.Release|Any CPU.ActiveCfg = Release|Any CPU
7581
{1BEB1E5C-26D3-42A2-B47A-7BF113822AA6}.Release|Any CPU.Build.0 = Release|Any CPU
82+
{A022F7A7-5E60-4DEE-B218-56FCA2464410}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
83+
{A022F7A7-5E60-4DEE-B218-56FCA2464410}.Debug|Any CPU.Build.0 = Debug|Any CPU
84+
{A022F7A7-5E60-4DEE-B218-56FCA2464410}.Release|Any CPU.ActiveCfg = Release|Any CPU
85+
{A022F7A7-5E60-4DEE-B218-56FCA2464410}.Release|Any CPU.Build.0 = Release|Any CPU
86+
{A5540922-A24D-461C-BE3B-B8FA85FECFDA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
87+
{A5540922-A24D-461C-BE3B-B8FA85FECFDA}.Debug|Any CPU.Build.0 = Debug|Any CPU
88+
{A5540922-A24D-461C-BE3B-B8FA85FECFDA}.Release|Any CPU.ActiveCfg = Release|Any CPU
89+
{A5540922-A24D-461C-BE3B-B8FA85FECFDA}.Release|Any CPU.Build.0 = Release|Any CPU
7690
EndGlobalSection
7791
GlobalSection(SolutionProperties) = preSolution
7892
HideSolutionNode = FALSE
@@ -86,6 +100,8 @@ Global
86100
{73A32BC7-2B54-4159-AB73-1D114A9CACBD} = {60EF4920-1A12-4E7F-822B-CE069E232A63}
87101
{52F1CEDD-5F42-4394-88D0-2B2C19B168C0} = {60EF4920-1A12-4E7F-822B-CE069E232A63}
88102
{1BEB1E5C-26D3-42A2-B47A-7BF113822AA6} = {60EF4920-1A12-4E7F-822B-CE069E232A63}
103+
{A022F7A7-5E60-4DEE-B218-56FCA2464410} = {D2AAE295-64F5-4B57-9E13-6D30FF4204FE}
104+
{A5540922-A24D-461C-BE3B-B8FA85FECFDA} = {D2AAE295-64F5-4B57-9E13-6D30FF4204FE}
89105
EndGlobalSection
90106
GlobalSection(ExtensibilityGlobals) = postSolution
91107
SolutionGuid = {5085DADC-BB1A-4E92-8CD6-A7CCC1DE312E}

readme.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,14 +31,15 @@ Not only it will be only a single line of code to use them with default configs,
3131
| ------------- | ------------- | ------------- |
3232
| ApiVersioning | [![](https://img.shields.io/nuget/v/TechBuddy.Extensions.AspNetCore.ApiVersioning?style=for-the-badge)](https://www.nuget.org/packages/TechBuddy.Extensions.AspNetCore.ApiVersioning) | [![](https://img.shields.io/nuget/dt/TechBuddy.Extensions.AspNetCore.ApiVersioning?style=for-the-badge)](https://www.nuget.org/packages/TechBuddy.Extensions.AspNetCore.ApiVersioning/) |
3333
| Validation | [![](https://img.shields.io/nuget/v/TechBuddy.Extensions.Validation?style=for-the-badge)](https://www.nuget.org/packages/TechBuddy.Extensions.Validation) | [![](https://img.shields.io/nuget/dt/TechBuddy.Extensions.Validation?style=for-the-badge)](https://www.nuget.org/packages/TTechBuddy.Extensions.Validation/) |
34+
| ExceptionHandling | [![](https://img.shields.io/nuget/v/TechBuddy.Extensions.ExceptionHandling?style=for-the-badge)](https://www.nuget.org/packages/TechBuddy.Extensions.ExceptionHandling) | [![](https://img.shields.io/nuget/dt/TechBuddy.Extensions.ExceptionHandling?style=for-the-badge)](https://www.nuget.org/packages/TTechBuddy.Extensions.ExceptionHandling/) |
3435

3536

3637
### Extensions
3738

3839
There are four different extension libraries planned which are;
3940

4041
- [ApiVersioning](https://github.com/TechBuddyTR/TechBuddy.Extensions/tree/dev/src/ApiVersioningExtension/ApiVersioningExtension)
41-
- Global Exception Handling
42+
- [Global Exception Handling](https://github.com/TechBuddyTR/TechBuddy.Extensions/tree/dev/src/ExceptionHandlingExtension/ExceptionHandlingExtension)
4243
- [Validations (FluentValidation based)](https://github.com/TechBuddyTR/TechBuddy.Extensions/tree/dev/src/ValidationExtension/ValidationExtension)
4344
- OpenApi Support (Swagger, Swagger UI)
4445

src/ApiVersioningExtension/ApiVersioningExtension/ApiVersioningExtension.csproj

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,8 @@
2727
<Description>Provides extension methods for ApiVersioning on your WebAPI project</Description>
2828
<PackageIcon>logo.png</PackageIcon>
2929
<Copyright>TechBuddyTR Youtube</Copyright>
30+
<PackageLicenseFile>LICENSE.txt</PackageLicenseFile>
31+
<PackageReadmeFile>README.md</PackageReadmeFile>
3032
<PackageProjectUrl>https://github.com/TechBuddyTR/TechBuddy.Extensions</PackageProjectUrl>
3133
<RepositoryUrl>https://github.com/TechBuddyTR/TechBuddy.Extensions</RepositoryUrl>
3234
<RepositoryType>git</RepositoryType>
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
<Project Sdk="Microsoft.NET.Sdk">
2+
3+
<PropertyGroup>
4+
<TargetFramework>net6.0</TargetFramework>
5+
<ImplicitUsings>enable</ImplicitUsings>
6+
<IsPackable>false</IsPackable>
7+
</PropertyGroup>
8+
9+
<ItemGroup>
10+
<PackageReference Include="FluentAssertions" Version="6.8.0" />
11+
<PackageReference Include="Microsoft.AspNetCore.Mvc.Versioning" Version="5.0.0" />
12+
<PackageReference Include="Microsoft.AspNetCore.TestHost" Version="6.0.10" />
13+
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.1.0" />
14+
<PackageReference Include="Moq" Version="4.18.2" />
15+
<PackageReference Include="NUnit" Version="3.13.3" />
16+
<PackageReference Include="NUnit3TestAdapter" Version="4.2.1" />
17+
<PackageReference Include="NUnit.Analyzers" Version="3.3.0" />
18+
<PackageReference Include="coverlet.collector" Version="3.1.2" />
19+
</ItemGroup>
20+
21+
<ItemGroup>
22+
<ProjectReference Include="..\..\AspNetCoreExtensions.Tests.Common\AspNetCoreExtensions.Tests.Common\AspNetCoreExtensions.Tests.Common.csproj" />
23+
<ProjectReference Include="..\ExceptionHandlingExtension\ExceptionHandlingExtension.csproj" />
24+
</ItemGroup>
25+
26+
</Project>
Lines changed: 293 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,293 @@
1+
using ExceptionHandlingExtension.Tests.Infrastructure.Extensions;
2+
using FluentAssertions;
3+
using Microsoft.AspNetCore.Builder;
4+
using Microsoft.AspNetCore.Hosting;
5+
using Microsoft.AspNetCore.TestHost;
6+
using Microsoft.Extensions.DependencyInjection;
7+
using Microsoft.Extensions.Hosting;
8+
using Microsoft.Extensions.Logging;
9+
using Moq;
10+
using System.Text.Json;
11+
using TechBuddy.Extensions.AspNetCore.ExceptionHandling;
12+
using TechBuddy.Extensions.AspNetCore.ExceptionHandling.Infrastructure.Models;
13+
using TechBuddy.Extensions.Tests.Common.TestCommon.Constants;
14+
15+
namespace ExceptionHandlingExtension.Tests;
16+
public sealed class ExceptionHandlingTests
17+
{
18+
[Test]
19+
public async Task ExceptionHandlingWithNoHandler_WithNoDetails_ShouldReturnInternalServerError()
20+
{
21+
// Arrange
22+
var server = GetServerWithOptions(new ExceptionHandlingOptions());
23+
var client = server.CreateClient();
24+
var request = new HttpRequestMessage(HttpMethod.Get, $"/api/Test/ThrowException");
25+
26+
// Action
27+
HttpResponseMessage response = await client.SendAsync(request);
28+
29+
// Assert
30+
response.IsSuccessStatusCode.Should().BeFalse();
31+
response.StatusCode.Should().Be(System.Net.HttpStatusCode.InternalServerError);
32+
}
33+
34+
[Test]
35+
public async Task ExceptionHandlingWithNoHandler_WhenUseExceptionDetailsIsFalse_ShouldReturnInternalServerErrorMessage()
36+
{
37+
// Arrange
38+
var server = GetServerWithOptions(new ExceptionHandlingOptions());
39+
var client = server.CreateClient();
40+
var request = new HttpRequestMessage(HttpMethod.Get, $"/api/Test/ThrowException");
41+
42+
// Action
43+
HttpResponseMessage response = await client.SendAsync(request);
44+
var responseContent = await response.Content.ReadAsStringAsync();
45+
46+
// Assert
47+
var resObj = JsonSerializer.Deserialize<DefaultExceptionHandlerResponseModel>(responseContent, GeneralConstants.JsonOptions);
48+
49+
resObj.Should().NotBeNull();
50+
resObj.Detail.Should().Be(TestConstants.DefaultExceptionMessage);
51+
}
52+
53+
[Test]
54+
public async Task ExceptionHandlingWithNoHandler_WhenUseExceptionDetailsIsTrue_ShouldReturnExceptionMessage()
55+
{
56+
// Arrange
57+
var options = new ExceptionHandlingOptions();
58+
options.UseLogger(true);
59+
var server = GetServerWithOptions(options);
60+
var client = server.CreateClient();
61+
var request = new HttpRequestMessage(HttpMethod.Get, $"/api/Test/ThrowException");
62+
63+
// Action
64+
HttpResponseMessage response = await client.SendAsync(request);
65+
var responseContent = await response.Content.ReadAsStringAsync();
66+
67+
68+
// Assert
69+
var resObj = JsonSerializer.Deserialize<DefaultExceptionHandlerResponseModel>(responseContent, GeneralConstants.JsonOptions);
70+
71+
resObj.Should().NotBeNull();
72+
resObj.Detail.Should().Contain(TestConstants.ExceptionMessage);
73+
}
74+
75+
[Test]
76+
public async Task ExceptionHandlingWithNoHandler_WithDetails_ShouldReturnInternalServer()
77+
{
78+
// Arrange
79+
var options = new ExceptionHandlingOptions();
80+
options.UseLogger(true);
81+
var server = GetServerWithOptions(options);
82+
var client = server.CreateClient();
83+
var request = new HttpRequestMessage(HttpMethod.Get, $"/api/Test/ThrowException");
84+
85+
// Action
86+
HttpResponseMessage response = await client.SendAsync(request);
87+
88+
89+
// Assert
90+
response.IsSuccessStatusCode.Should().BeFalse();
91+
response.StatusCode.Should().Be(System.Net.HttpStatusCode.InternalServerError);
92+
}
93+
94+
[Test]
95+
public async Task ExceptionHandlingWithNoHandler_WithDetails_ShouldReturnExceptionMessage()
96+
{
97+
// Arrange
98+
var options = new ExceptionHandlingOptions();
99+
options.UseLogger(true);
100+
var server = GetServerWithOptions(options);
101+
var client = server.CreateClient();
102+
var request = new HttpRequestMessage(HttpMethod.Get, $"/api/Test/ThrowException");
103+
104+
// Action
105+
HttpResponseMessage response = await client.SendAsync(request);
106+
var responseContent = await response.Content.ReadAsStringAsync();
107+
108+
// Assert
109+
response.IsSuccessStatusCode.Should().BeFalse();
110+
response.StatusCode.Should().Be(System.Net.HttpStatusCode.InternalServerError);
111+
112+
var resObj = JsonSerializer.Deserialize<DefaultExceptionHandlerResponseModel>(responseContent, GeneralConstants.JsonOptions);
113+
114+
resObj.Should().NotBeNull();
115+
resObj.Detail.Should().StartWith($"System.Exception: {TestConstants.ExceptionMessage}");
116+
}
117+
118+
[Test]
119+
public async Task ExceptionHandling_WithHandler_ShouldReturnExceptionMessage()
120+
{
121+
// Arrange && Assert
122+
var opt = new ExceptionHandlingOptions();
123+
opt.UseCustomHandler(async (ctx, ex, logger) =>
124+
{
125+
ex.Message.Should().NotBeNull();
126+
ex.Message.Should().Be(TestConstants.ExceptionMessage);
127+
128+
await Task.CompletedTask; // expects returning task
129+
});
130+
131+
var server = GetServerWithOptions(opt);
132+
var client = server.CreateClient();
133+
var request = new HttpRequestMessage(HttpMethod.Get, $"/api/Test/ThrowException");
134+
135+
// Action
136+
HttpResponseMessage response = await client.SendAsync(request);
137+
}
138+
139+
140+
[Test]
141+
public async Task ExceptionHandlingWithHandler_WhenWriteToResponse_ShouldReturnTheMessage()
142+
{
143+
// Arrange && Assert
144+
var opt = new ExceptionHandlingOptions();
145+
opt.UseCustomHandler(async (ctx, ex, logger) =>
146+
{
147+
ex.Message.Should().NotBeNull();
148+
ex.Message.Should().Be(TestConstants.ExceptionMessage);
149+
150+
// writes as json, so it's like "TestConstants.ExceptionMessage"
151+
await ctx.WriteResponseAsync(TestConstants.ExceptionMessage, System.Net.HttpStatusCode.InternalServerError);
152+
});
153+
154+
var server = GetServerWithOptions(opt);
155+
var client = server.CreateClient();
156+
var request = new HttpRequestMessage(HttpMethod.Get, $"/api/Test/ThrowException");
157+
158+
// Action
159+
HttpResponseMessage response = await client.SendAsync(request);
160+
var responseContent = await response.Content.ReadAsStringAsync();
161+
162+
// Assert
163+
response.StatusCode.Should().Be(System.Net.HttpStatusCode.InternalServerError);
164+
responseContent.Should().Be($"\"{TestConstants.ExceptionMessage}\"");
165+
}
166+
167+
168+
[Test]
169+
public async Task ExceptionHandlingWithNoHandler_WithCustomLogger_ShouldCallLogError()
170+
{
171+
// Arrange
172+
Mock<ILogger> mockLogger = new Mock<ILogger>();
173+
var opt = new ExceptionHandlingOptions();
174+
opt.UseLogger(mockLogger.Object);
175+
var server = GetServerWithOptions(opt);
176+
var client = server.CreateClient();
177+
var request = new HttpRequestMessage(HttpMethod.Get, $"/api/Test/ThrowException");
178+
179+
// Action
180+
HttpResponseMessage response = await client.SendAsync(request);
181+
182+
// Assert
183+
mockLogger.Verify(m => m.Log(LogLevel.Error,
184+
It.IsAny<EventId>(),
185+
It.Is<It.IsAnyType>((v, t) => true),
186+
It.IsAny<Exception>(),
187+
It.IsAny<Func<It.IsAnyType, Exception, string>>()), Times.Once);
188+
}
189+
190+
[Test]
191+
public async Task ExceptionHandlingWithNoHandler_WithCustomLogger_ShouldCallLogErrorWithMessageDetails()
192+
{
193+
// Arrange
194+
var expectedMessage = TestConstants.ExceptionMessage;
195+
Mock<ILogger> mockLogger = new Mock<ILogger>();
196+
var opt = new ExceptionHandlingOptions();
197+
opt.UseLogger(mockLogger.Object, true);
198+
var server = GetServerWithOptions(opt);
199+
var client = server.CreateClient();
200+
var request = new HttpRequestMessage(HttpMethod.Get, $"/api/Test/ThrowException");
201+
202+
// Action
203+
HttpResponseMessage response = await client.SendAsync(request);
204+
205+
// Assert
206+
mockLogger.Verify(logger => logger.Log(LogLevel.Error,
207+
It.IsAny<EventId>(),
208+
It.Is<It.IsAnyType>((v, _) => v.ToString().Contains(expectedMessage)),
209+
It.IsAny<Exception>(),
210+
(Func<It.IsAnyType, Exception, string>)It.IsAny<object>()),
211+
Times.Once);
212+
}
213+
214+
[Test]
215+
public async Task ExceptionHandling_WithSingleCustomHandler_ShouldBeCalled()
216+
{
217+
// Assert
218+
var functionWasInvoked = false;
219+
var expectedMessage = TestConstants.ExceptionMessage;
220+
var options = new ExceptionHandlingOptions();
221+
options.UseLogger(true);
222+
223+
options.AddCustomHandler<Exception>((context, ex, logger) =>
224+
{
225+
functionWasInvoked = true;
226+
ex.Message.Should().Be(expectedMessage);
227+
return Task.CompletedTask;
228+
});
229+
230+
var server = GetServerWithOptions(options);
231+
var client = server.CreateClient();
232+
var request = new HttpRequestMessage(HttpMethod.Get, $"/api/Test/ThrowException");
233+
234+
// Action
235+
HttpResponseMessage response = await client.SendAsync(request);
236+
237+
// Assert
238+
functionWasInvoked.Should().BeTrue();
239+
}
240+
241+
[Test]
242+
public async Task ExceptionHandlingWithCustomHandler_WithUseHandler_ShouldBeCalled()
243+
{
244+
// Assert
245+
bool functionWasInvoked = false;
246+
var options = new ExceptionHandlingOptions();
247+
248+
options.UseCustomHandler((context, ex, logger) =>
249+
{
250+
functionWasInvoked = false;
251+
return Task.CompletedTask;
252+
});
253+
254+
options.AddCustomHandler<InvalidOperationException>((context, ex, logger) =>
255+
{
256+
functionWasInvoked = true;
257+
return Task.CompletedTask;
258+
});
259+
260+
var server = GetServerWithOptions(options);
261+
var client = server.CreateClient();
262+
var request = new HttpRequestMessage(HttpMethod.Get, $"/api/Test/ThrowCustomException");
263+
264+
// Action
265+
HttpResponseMessage response = await client.SendAsync(request);
266+
267+
// Assert
268+
functionWasInvoked.Should().BeTrue();
269+
}
270+
271+
#region Private Methods
272+
273+
private static TestServer GetServerWithOptions(ExceptionHandlingOptions options)
274+
{
275+
var hostBuilder = new WebHostBuilder()
276+
.ConfigureServices(services =>
277+
{
278+
services.AddMvc(i => i.EnableEndpointRouting = false);
279+
})
280+
.Configure(async (app) =>
281+
{
282+
var env = app.ApplicationServices.GetService<IWebHostEnvironment>();
283+
284+
await app.ConfigureTechBuddyExceptionHandling(options);
285+
286+
app.UseMvc();
287+
});
288+
289+
return new TestServer(hostBuilder);
290+
}
291+
292+
#endregion
293+
}

0 commit comments

Comments
 (0)