1+ using System . Collections . Generic ;
2+ using FluentAssertions ;
3+ using ManagedCode . Communication . Extensions ;
4+ using Microsoft . AspNetCore . Mvc ;
5+ using Xunit ;
6+
7+ namespace ManagedCode . Communication . Tests . Extensions ;
8+
9+ public class ProblemExtensionsTests
10+ {
11+ [ Fact ]
12+ public void ToProblemDetails_ShouldConvertProblemToProblemDetails ( )
13+ {
14+ // Arrange
15+ var problem = Problem . Create ( "https://httpstatuses.io/400" , "Bad Request" , 400 , "Invalid input" , "/api/users" ) ;
16+ problem . Extensions [ "traceId" ] = "12345" ;
17+ problem . Extensions [ "timestamp" ] = "2024-01-01T00:00:00Z" ;
18+
19+ // Act
20+ var problemDetails = problem . ToProblemDetails ( ) ;
21+
22+ // Assert
23+ problemDetails . Should ( ) . NotBeNull ( ) ;
24+ problemDetails . Type . Should ( ) . Be ( "https://httpstatuses.io/400" ) ;
25+ problemDetails . Title . Should ( ) . Be ( "Bad Request" ) ;
26+ problemDetails . Status . Should ( ) . Be ( 400 ) ;
27+ problemDetails . Detail . Should ( ) . Be ( "Invalid input" ) ;
28+ problemDetails . Instance . Should ( ) . Be ( "/api/users" ) ;
29+ problemDetails . Extensions . Should ( ) . ContainKey ( "traceId" ) ;
30+ problemDetails . Extensions [ "traceId" ] . Should ( ) . Be ( "12345" ) ;
31+ problemDetails . Extensions . Should ( ) . ContainKey ( "timestamp" ) ;
32+ problemDetails . Extensions [ "timestamp" ] . Should ( ) . Be ( "2024-01-01T00:00:00Z" ) ;
33+ }
34+
35+ [ Fact ]
36+ public void ToProblemDetails_WithZeroStatusCode_ShouldSetStatusToNull ( )
37+ {
38+ // Arrange
39+ var problem = new Problem
40+ {
41+ Type = "https://example.com/error" ,
42+ Title = "Error" ,
43+ StatusCode = 0 ,
44+ Detail = "Something went wrong"
45+ } ;
46+
47+ // Act
48+ var problemDetails = problem . ToProblemDetails ( ) ;
49+
50+ // Assert
51+ problemDetails . Status . Should ( ) . BeNull ( ) ;
52+ }
53+
54+ [ Fact ]
55+ public void FromProblemDetails_ShouldConvertProblemDetailsToProblem ( )
56+ {
57+ // Arrange
58+ var problemDetails = new ProblemDetails
59+ {
60+ Type = "https://httpstatuses.io/404" ,
61+ Title = "Not Found" ,
62+ Status = 404 ,
63+ Detail = "Resource not found" ,
64+ Instance = "/api/items/123"
65+ } ;
66+ problemDetails . Extensions [ "correlationId" ] = "abc-123" ;
67+ problemDetails . Extensions [ "userId" ] = 42 ;
68+
69+ // Act
70+ var problem = ProblemExtensions . FromProblemDetails ( problemDetails ) ;
71+
72+ // Assert
73+ problem . Should ( ) . NotBeNull ( ) ;
74+ problem . Type . Should ( ) . Be ( "https://httpstatuses.io/404" ) ;
75+ problem . Title . Should ( ) . Be ( "Not Found" ) ;
76+ problem . StatusCode . Should ( ) . Be ( 404 ) ;
77+ problem . Detail . Should ( ) . Be ( "Resource not found" ) ;
78+ problem . Instance . Should ( ) . Be ( "/api/items/123" ) ;
79+ problem . Extensions . Should ( ) . ContainKey ( "correlationId" ) ;
80+ problem . Extensions [ "correlationId" ] . Should ( ) . Be ( "abc-123" ) ;
81+ problem . Extensions . Should ( ) . ContainKey ( "userId" ) ;
82+ problem . Extensions [ "userId" ] . Should ( ) . Be ( 42 ) ;
83+ }
84+
85+ [ Fact ]
86+ public void FromProblemDetails_WithNullStatus_ShouldSetStatusCodeToZero ( )
87+ {
88+ // Arrange
89+ var problemDetails = new ProblemDetails
90+ {
91+ Type = "https://example.com/error" ,
92+ Title = "Error" ,
93+ Status = null ,
94+ Detail = "Something went wrong"
95+ } ;
96+
97+ // Act
98+ var problem = ProblemExtensions . FromProblemDetails ( problemDetails ) ;
99+
100+ // Assert
101+ problem . StatusCode . Should ( ) . Be ( 0 ) ;
102+ }
103+
104+ [ Fact ]
105+ public void AsProblemDetails_ShouldConvertProblemToProblemDetails ( )
106+ {
107+ // Arrange
108+ var problem = Problem . Create ( "type" , "title" , 400 , "detail" ) ;
109+
110+ // Act
111+ var problemDetails = problem . AsProblemDetails ( ) ;
112+
113+ // Assert
114+ problemDetails . Should ( ) . NotBeNull ( ) ;
115+ problemDetails . Type . Should ( ) . Be ( "type" ) ;
116+ problemDetails . Title . Should ( ) . Be ( "title" ) ;
117+ problemDetails . Status . Should ( ) . Be ( 400 ) ;
118+ problemDetails . Detail . Should ( ) . Be ( "detail" ) ;
119+ }
120+
121+ [ Fact ]
122+ public void AsProblem_ShouldConvertProblemDetailsToProblem ( )
123+ {
124+ // Arrange
125+ var problemDetails = new ProblemDetails
126+ {
127+ Type = "type" ,
128+ Title = "title" ,
129+ Status = 500 ,
130+ Detail = "detail"
131+ } ;
132+
133+ // Act
134+ var problem = problemDetails . AsProblem ( ) ;
135+
136+ // Assert
137+ problem . Should ( ) . NotBeNull ( ) ;
138+ problem . Type . Should ( ) . Be ( "type" ) ;
139+ problem . Title . Should ( ) . Be ( "title" ) ;
140+ problem . StatusCode . Should ( ) . Be ( 500 ) ;
141+ problem . Detail . Should ( ) . Be ( "detail" ) ;
142+ }
143+
144+ [ Fact ]
145+ public void ToFailedResult_FromProblemDetails_ShouldCreateFailedResult ( )
146+ {
147+ // Arrange
148+ var problemDetails = new ProblemDetails
149+ {
150+ Type = "https://httpstatuses.io/400" ,
151+ Title = "Validation Error" ,
152+ Status = 400 ,
153+ Detail = "Invalid input data"
154+ } ;
155+
156+ // Act
157+ var result = problemDetails . ToFailedResult ( ) ;
158+
159+ // Assert
160+ result . IsFailed . Should ( ) . BeTrue ( ) ;
161+ result . IsSuccess . Should ( ) . BeFalse ( ) ;
162+ result . Problem . Should ( ) . NotBeNull ( ) ;
163+ result . Problem ! . Type . Should ( ) . Be ( "https://httpstatuses.io/400" ) ;
164+ result . Problem . Title . Should ( ) . Be ( "Validation Error" ) ;
165+ result . Problem . StatusCode . Should ( ) . Be ( 400 ) ;
166+ result . Problem . Detail . Should ( ) . Be ( "Invalid input data" ) ;
167+ }
168+
169+ [ Fact ]
170+ public void ToFailedResultT_FromProblemDetails_ShouldCreateFailedResultT ( )
171+ {
172+ // Arrange
173+ var problemDetails = new ProblemDetails
174+ {
175+ Type = "https://httpstatuses.io/404" ,
176+ Title = "Not Found" ,
177+ Status = 404 ,
178+ Detail = "User not found"
179+ } ;
180+
181+ // Act
182+ var result = problemDetails . ToFailedResult < string > ( ) ;
183+
184+ // Assert
185+ result . IsFailed . Should ( ) . BeTrue ( ) ;
186+ result . IsSuccess . Should ( ) . BeFalse ( ) ;
187+ result . Value . Should ( ) . BeNull ( ) ;
188+ result . Problem . Should ( ) . NotBeNull ( ) ;
189+ result . Problem ! . Type . Should ( ) . Be ( "https://httpstatuses.io/404" ) ;
190+ result . Problem . Title . Should ( ) . Be ( "Not Found" ) ;
191+ result . Problem . StatusCode . Should ( ) . Be ( 404 ) ;
192+ result . Problem . Detail . Should ( ) . Be ( "User not found" ) ;
193+ }
194+
195+ [ Fact ]
196+ public void ToFailedResult_FromProblem_ShouldCreateFailedResult ( )
197+ {
198+ // Arrange
199+ var problem = Problem . Create ( "https://httpstatuses.io/500" , "Server Error" , 500 , "Internal error occurred" ) ;
200+
201+ // Act
202+ var result = problem . ToFailedResult ( ) ;
203+
204+ // Assert
205+ result . IsFailed . Should ( ) . BeTrue ( ) ;
206+ result . IsSuccess . Should ( ) . BeFalse ( ) ;
207+ result . Problem . Should ( ) . Be ( problem ) ;
208+ }
209+
210+ [ Fact ]
211+ public void ToFailedResultT_FromProblem_ShouldCreateFailedResultT ( )
212+ {
213+ // Arrange
214+ var problem = Problem . Create ( "https://httpstatuses.io/403" , "Forbidden" , 403 , "Access denied" ) ;
215+
216+ // Act
217+ var result = problem . ToFailedResult < int > ( ) ;
218+
219+ // Assert
220+ result . IsFailed . Should ( ) . BeTrue ( ) ;
221+ result . IsSuccess . Should ( ) . BeFalse ( ) ;
222+ result . Value . Should ( ) . Be ( default ( int ) ) ;
223+ result . Problem . Should ( ) . Be ( problem ) ;
224+ }
225+
226+ [ Fact ]
227+ public void RoundTrip_ProblemToProblemDetailsAndBack_ShouldPreserveAllData ( )
228+ {
229+ // Arrange
230+ var originalProblem = Problem . Create ( "https://httpstatuses.io/409" , "Conflict" , 409 , "Resource conflict" , "/api/resource/123" ) ;
231+ originalProblem . Extensions [ "error_code" ] = "RESOURCE_LOCKED" ;
232+ originalProblem . Extensions [ "retry_after" ] = 30 ;
233+ originalProblem . Extensions [ "nested" ] = new Dictionary < string , object > { [ "key" ] = "value" } ;
234+
235+ // Act
236+ var problemDetails = originalProblem . ToProblemDetails ( ) ;
237+ var convertedProblem = problemDetails . AsProblem ( ) ;
238+
239+ // Assert
240+ convertedProblem . Type . Should ( ) . Be ( originalProblem . Type ) ;
241+ convertedProblem . Title . Should ( ) . Be ( originalProblem . Title ) ;
242+ convertedProblem . StatusCode . Should ( ) . Be ( originalProblem . StatusCode ) ;
243+ convertedProblem . Detail . Should ( ) . Be ( originalProblem . Detail ) ;
244+ convertedProblem . Instance . Should ( ) . Be ( originalProblem . Instance ) ;
245+ convertedProblem . Extensions . Should ( ) . BeEquivalentTo ( originalProblem . Extensions ) ;
246+ }
247+
248+ [ Fact ]
249+ public void RoundTrip_ProblemDetailsWithNullValues_ShouldHandleGracefully ( )
250+ {
251+ // Arrange
252+ var problemDetails = new ProblemDetails
253+ {
254+ Type = null ,
255+ Title = null ,
256+ Status = null ,
257+ Detail = null ,
258+ Instance = null
259+ } ;
260+
261+ // Act
262+ var problem = problemDetails . AsProblem ( ) ;
263+ var convertedProblemDetails = problem . AsProblemDetails ( ) ;
264+
265+ // Assert
266+ problem . Type . Should ( ) . BeNull ( ) ;
267+ problem . Title . Should ( ) . BeNull ( ) ;
268+ problem . StatusCode . Should ( ) . Be ( 0 ) ;
269+ problem . Detail . Should ( ) . BeNull ( ) ;
270+ problem . Instance . Should ( ) . BeNull ( ) ;
271+
272+ convertedProblemDetails . Type . Should ( ) . BeNull ( ) ;
273+ convertedProblemDetails . Title . Should ( ) . BeNull ( ) ;
274+ convertedProblemDetails . Status . Should ( ) . BeNull ( ) ;
275+ convertedProblemDetails . Detail . Should ( ) . BeNull ( ) ;
276+ convertedProblemDetails . Instance . Should ( ) . BeNull ( ) ;
277+ }
278+
279+ [ Fact ]
280+ public void ToFailedResult_WithComplexExtensions_ShouldPreserveAllData ( )
281+ {
282+ // Arrange
283+ var problemDetails = new ProblemDetails
284+ {
285+ Type = "https://httpstatuses.io/422" ,
286+ Title = "Unprocessable Entity" ,
287+ Status = 422 ,
288+ Detail = "Validation failed"
289+ } ;
290+ problemDetails . Extensions [ "errors" ] = new Dictionary < string , List < string > >
291+ {
292+ [ "email" ] = new List < string > { "Invalid format" , "Already exists" } ,
293+ [ "password" ] = new List < string > { "Too short" }
294+ } ;
295+
296+ // Act
297+ var result = problemDetails . ToFailedResult ( ) ;
298+
299+ // Assert
300+ result . Problem ! . Extensions . Should ( ) . ContainKey ( "errors" ) ;
301+ var errors = result . Problem . Extensions [ "errors" ] as Dictionary < string , List < string > > ;
302+ errors . Should ( ) . NotBeNull ( ) ;
303+ errors ! [ "email" ] . Should ( ) . Contain ( "Invalid format" ) ;
304+ errors [ "email" ] . Should ( ) . Contain ( "Already exists" ) ;
305+ errors [ "password" ] . Should ( ) . Contain ( "Too short" ) ;
306+ }
307+ }
0 commit comments