1+ using CoreZipCode . Interfaces ;
2+ using Moq ;
3+ using Moq . Protected ;
4+ using System ;
5+ using System . Net ;
6+ using System . Net . Http ;
7+ using System . Threading ;
8+ using System . Threading . Tasks ;
9+ using Xunit ;
10+
11+ namespace CoreZipCode . Tests . Interfaces
12+ {
13+ public class ApiHandlerTest
14+ {
15+ [ Fact ]
16+ public void Constructor_Without_Parameters_Should_Create_Instance ( )
17+ {
18+ var handler = new ApiHandler ( ) ;
19+
20+ Assert . NotNull ( handler ) ;
21+ }
22+
23+ [ Fact ]
24+ public void Constructor_With_HttpClient_Should_Create_Instance ( )
25+ {
26+ var httpClient = new HttpClient ( ) ;
27+
28+ var handler = new ApiHandler ( httpClient ) ;
29+
30+ Assert . NotNull ( handler ) ;
31+ }
32+
33+ [ Fact ]
34+ public void Constructor_With_Null_HttpClient_Should_Throw_ArgumentNullException ( )
35+ {
36+ Assert . Throws < ArgumentNullException > ( ( ) => new ApiHandler ( null ) ) ;
37+ }
38+
39+ [ Fact ]
40+ public async Task CallApiAsync_With_Null_Url_Should_Return_BadRequest ( )
41+ {
42+ var handler = new ApiHandler ( ) ;
43+
44+ var result = await handler . CallApiAsync ( null ) ;
45+
46+ Assert . True ( result . IsFailure ) ;
47+ Assert . Equal ( HttpStatusCode . BadRequest , result . Error . StatusCode ) ;
48+ Assert . Equal ( "URL cannot be null or empty." , result . Error . Message ) ;
49+ }
50+
51+ [ Fact ]
52+ public async Task CallApiAsync_With_Empty_Url_Should_Return_BadRequest ( )
53+ {
54+ var handler = new ApiHandler ( ) ;
55+
56+ var result = await handler . CallApiAsync ( string . Empty ) ;
57+
58+ Assert . True ( result . IsFailure ) ;
59+ Assert . Equal ( HttpStatusCode . BadRequest , result . Error . StatusCode ) ;
60+ Assert . Equal ( "URL cannot be null or empty." , result . Error . Message ) ;
61+ }
62+
63+ [ Fact ]
64+ public async Task CallApiAsync_With_Whitespace_Url_Should_Return_BadRequest ( )
65+ {
66+ var handler = new ApiHandler ( ) ;
67+
68+ var result = await handler . CallApiAsync ( " " ) ;
69+
70+ Assert . True ( result . IsFailure ) ;
71+ Assert . Equal ( HttpStatusCode . BadRequest , result . Error . StatusCode ) ;
72+ Assert . Equal ( "URL cannot be null or empty." , result . Error . Message ) ;
73+ }
74+
75+ [ Fact ]
76+ public async Task CallApiAsync_With_Success_Response_Should_Return_Success ( )
77+ {
78+ var expectedBody = "{\" data\" :\" test\" }" ;
79+ var handlerMock = new Mock < HttpMessageHandler > ( ) ;
80+ handlerMock
81+ . Protected ( )
82+ . Setup < Task < HttpResponseMessage > > (
83+ "SendAsync" ,
84+ ItExpr . IsAny < HttpRequestMessage > ( ) ,
85+ ItExpr . IsAny < CancellationToken > ( )
86+ )
87+ . ReturnsAsync ( new HttpResponseMessage
88+ {
89+ StatusCode = HttpStatusCode . OK ,
90+ Content = new StringContent ( expectedBody )
91+ } ) ;
92+ var httpClient = new HttpClient ( handlerMock . Object ) ;
93+ var handler = new ApiHandler ( httpClient ) ;
94+
95+ var result = await handler . CallApiAsync ( "https://test.com" ) ;
96+
97+ Assert . True ( result . IsSuccess ) ;
98+ Assert . Equal ( expectedBody , result . Value ) ;
99+ }
100+
101+ [ Fact ]
102+ public async Task CallApiAsync_With_NonSuccess_Response_Should_Return_Failure ( )
103+ {
104+ var responseBody = "{\" error\" :\" not found\" }" ;
105+ var handlerMock = new Mock < HttpMessageHandler > ( ) ;
106+ handlerMock
107+ . Protected ( )
108+ . Setup < Task < HttpResponseMessage > > (
109+ "SendAsync" ,
110+ ItExpr . IsAny < HttpRequestMessage > ( ) ,
111+ ItExpr . IsAny < CancellationToken > ( )
112+ )
113+ . ReturnsAsync ( new HttpResponseMessage
114+ {
115+ StatusCode = HttpStatusCode . NotFound ,
116+ ReasonPhrase = "Not Found" ,
117+ Content = new StringContent ( responseBody )
118+ } ) ;
119+ var httpClient = new HttpClient ( handlerMock . Object ) ;
120+ var handler = new ApiHandler ( httpClient ) ;
121+
122+ var result = await handler . CallApiAsync ( "https://test.com" ) ;
123+
124+ Assert . True ( result . IsFailure ) ;
125+ Assert . Equal ( HttpStatusCode . NotFound , result . Error . StatusCode ) ;
126+ Assert . Contains ( "404" , result . Error . Message ) ;
127+ Assert . Contains ( "NotFound" , result . Error . Message ) ;
128+ Assert . Equal ( responseBody , result . Error . ResponseBody ) ;
129+ }
130+
131+ [ Fact ]
132+ public async Task CallApiAsync_With_HttpRequestException_Should_Return_ServiceUnavailable ( )
133+ {
134+ var handlerMock = new Mock < HttpMessageHandler > ( ) ;
135+ handlerMock
136+ . Protected ( )
137+ . Setup < Task < HttpResponseMessage > > (
138+ "SendAsync" ,
139+ ItExpr . IsAny < HttpRequestMessage > ( ) ,
140+ ItExpr . IsAny < CancellationToken > ( )
141+ )
142+ . ThrowsAsync ( new HttpRequestException ( "Connection failed" ) ) ;
143+ var httpClient = new HttpClient ( handlerMock . Object ) ;
144+ var handler = new ApiHandler ( httpClient ) ;
145+
146+ var result = await handler . CallApiAsync ( "https://test.com" ) ;
147+
148+ Assert . True ( result . IsFailure ) ;
149+ Assert . Equal ( HttpStatusCode . ServiceUnavailable , result . Error . StatusCode ) ;
150+ Assert . Equal ( "Network or connection error." , result . Error . Message ) ;
151+ }
152+
153+ [ Fact ]
154+ public async Task CallApiAsync_With_TaskCanceledException_Should_Return_RequestTimeout ( )
155+ {
156+ var handlerMock = new Mock < HttpMessageHandler > ( ) ;
157+ handlerMock
158+ . Protected ( )
159+ . Setup < Task < HttpResponseMessage > > (
160+ "SendAsync" ,
161+ ItExpr . IsAny < HttpRequestMessage > ( ) ,
162+ ItExpr . IsAny < CancellationToken > ( )
163+ )
164+ . ThrowsAsync ( new TaskCanceledException ( "The request timed out" ) ) ;
165+ var httpClient = new HttpClient ( handlerMock . Object ) ;
166+ var handler = new ApiHandler ( httpClient ) ;
167+
168+ var result = await handler . CallApiAsync ( "https://test.com" ) ;
169+
170+ Assert . True ( result . IsFailure ) ;
171+ Assert . Equal ( HttpStatusCode . RequestTimeout , result . Error . StatusCode ) ;
172+ Assert . Equal ( "Request timed out." , result . Error . Message ) ;
173+ }
174+
175+ [ Fact ]
176+ public async Task CallApiAsync_With_OperationCanceledException_Should_Return_BadRequest ( )
177+ {
178+ var handlerMock = new Mock < HttpMessageHandler > ( ) ;
179+ handlerMock
180+ . Protected ( )
181+ . Setup < Task < HttpResponseMessage > > (
182+ "SendAsync" ,
183+ ItExpr . IsAny < HttpRequestMessage > ( ) ,
184+ ItExpr . IsAny < CancellationToken > ( )
185+ )
186+ . ThrowsAsync ( new OperationCanceledException ( "Operation was cancelled" ) ) ;
187+ var httpClient = new HttpClient ( handlerMock . Object ) ;
188+ var handler = new ApiHandler ( httpClient ) ;
189+
190+ var result = await handler . CallApiAsync ( "https://test.com" ) ;
191+
192+ Assert . True ( result . IsFailure ) ;
193+ Assert . Equal ( HttpStatusCode . BadRequest , result . Error . StatusCode ) ;
194+ Assert . Equal ( "Request was cancelled." , result . Error . Message ) ;
195+ }
196+
197+ [ Fact ]
198+ public async Task CallApiAsync_With_Generic_Exception_Should_Return_InternalServerError ( )
199+ {
200+ var handlerMock = new Mock < HttpMessageHandler > ( ) ;
201+ handlerMock
202+ . Protected ( )
203+ . Setup < Task < HttpResponseMessage > > (
204+ "SendAsync" ,
205+ ItExpr . IsAny < HttpRequestMessage > ( ) ,
206+ ItExpr . IsAny < CancellationToken > ( )
207+ )
208+ . ThrowsAsync ( new InvalidOperationException ( "Something went wrong" ) ) ;
209+ var httpClient = new HttpClient ( handlerMock . Object ) ;
210+ var handler = new ApiHandler ( httpClient ) ;
211+
212+ var result = await handler . CallApiAsync ( "https://test.com" ) ;
213+
214+ Assert . True ( result . IsFailure ) ;
215+ Assert . Equal ( HttpStatusCode . InternalServerError , result . Error . StatusCode ) ;
216+ Assert . Equal ( "Unexpected error." , result . Error . Message ) ;
217+ }
218+
219+ [ Fact ]
220+ public async Task CallApiAsync_With_Multiple_Success_Codes_Should_Return_Success ( )
221+ {
222+ var testCases = new [ ]
223+ {
224+ HttpStatusCode . OK ,
225+ HttpStatusCode . Created ,
226+ HttpStatusCode . Accepted ,
227+ HttpStatusCode . NoContent
228+ } ;
229+
230+ foreach ( var statusCode in testCases )
231+ {
232+ var handlerMock = new Mock < HttpMessageHandler > ( ) ;
233+ handlerMock
234+ . Protected ( )
235+ . Setup < Task < HttpResponseMessage > > (
236+ "SendAsync" ,
237+ ItExpr . IsAny < HttpRequestMessage > ( ) ,
238+ ItExpr . IsAny < CancellationToken > ( )
239+ )
240+ . ReturnsAsync ( new HttpResponseMessage
241+ {
242+ StatusCode = statusCode ,
243+ Content = new StringContent ( "success" )
244+ } ) ;
245+ var httpClient = new HttpClient ( handlerMock . Object ) ;
246+ var handler = new ApiHandler ( httpClient ) ;
247+
248+ var result = await handler . CallApiAsync ( "https://test.com" ) ;
249+
250+ Assert . True ( result . IsSuccess ) ;
251+ }
252+ }
253+
254+ [ Fact ]
255+ public async Task CallApiAsync_With_Multiple_Error_Codes_Should_Return_Failure ( )
256+ {
257+ var testCases = new [ ]
258+ {
259+ HttpStatusCode . BadRequest ,
260+ HttpStatusCode . Unauthorized ,
261+ HttpStatusCode . Forbidden ,
262+ HttpStatusCode . NotFound ,
263+ HttpStatusCode . InternalServerError ,
264+ HttpStatusCode . BadGateway ,
265+ HttpStatusCode . ServiceUnavailable
266+ } ;
267+
268+ foreach ( var statusCode in testCases )
269+ {
270+ var handlerMock = new Mock < HttpMessageHandler > ( ) ;
271+ handlerMock
272+ . Protected ( )
273+ . Setup < Task < HttpResponseMessage > > (
274+ "SendAsync" ,
275+ ItExpr . IsAny < HttpRequestMessage > ( ) ,
276+ ItExpr . IsAny < CancellationToken > ( )
277+ )
278+ . ReturnsAsync ( new HttpResponseMessage
279+ {
280+ StatusCode = statusCode ,
281+ Content = new StringContent ( "error" )
282+ } ) ;
283+ var httpClient = new HttpClient ( handlerMock . Object ) ;
284+ var handler = new ApiHandler ( httpClient ) ;
285+
286+ var result = await handler . CallApiAsync ( "https://test.com" ) ;
287+
288+ Assert . True ( result . IsFailure ) ;
289+ Assert . Equal ( statusCode , result . Error . StatusCode ) ;
290+ }
291+ }
292+ }
293+ }
0 commit comments