-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathCustomErrorControllerTest.java
More file actions
206 lines (162 loc) · 8.89 KB
/
CustomErrorControllerTest.java
File metadata and controls
206 lines (162 loc) · 8.89 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
package contactapp;
import contactapp.api.CustomErrorController;
import contactapp.api.dto.ErrorResponse;
import jakarta.servlet.RequestDispatcher;
import jakarta.servlet.http.HttpServletRequest;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.CsvSource;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
/**
* Unit tests for {@link CustomErrorController}.
*
* <p>Tests the error controller that ensures most errors return JSON responses,
* including those rejected at the servlet container level before reaching
* Spring MVC's exception handling.
*
* <p>This controller (combined with JsonErrorReportValve) ensures API consumers
* receive parseable JSON for normal error scenarios. Note: Extremely malformed
* URLs that fail at Tomcat's connector level may still return HTML; see ADR-0022.
*/
class CustomErrorControllerTest {
private CustomErrorController controller;
private HttpServletRequest mockRequest;
@BeforeEach
void setUp() {
controller = new CustomErrorController();
mockRequest = mock(HttpServletRequest.class);
}
@Test
void handleError_returnsJsonContentType() {
when(mockRequest.getAttribute(RequestDispatcher.ERROR_STATUS_CODE)).thenReturn(400);
when(mockRequest.getAttribute(RequestDispatcher.ERROR_MESSAGE)).thenReturn(null);
final ResponseEntity<ErrorResponse> response = controller.handleError(mockRequest);
assertEquals(MediaType.APPLICATION_JSON, response.getHeaders().getContentType());
}
@Test
void handleError_badRequest_returnsStatus400() {
when(mockRequest.getAttribute(RequestDispatcher.ERROR_STATUS_CODE)).thenReturn(400);
when(mockRequest.getAttribute(RequestDispatcher.ERROR_MESSAGE)).thenReturn(null);
final ResponseEntity<ErrorResponse> response = controller.handleError(mockRequest);
assertEquals(HttpStatus.BAD_REQUEST, response.getStatusCode());
assertNotNull(response.getBody());
assertEquals("Bad request", response.getBody().message());
}
@Test
void handleError_notFound_returnsStatus404() {
when(mockRequest.getAttribute(RequestDispatcher.ERROR_STATUS_CODE)).thenReturn(404);
when(mockRequest.getAttribute(RequestDispatcher.ERROR_MESSAGE)).thenReturn(null);
final ResponseEntity<ErrorResponse> response = controller.handleError(mockRequest);
assertEquals(HttpStatus.NOT_FOUND, response.getStatusCode());
assertNotNull(response.getBody());
assertEquals("Resource not found", response.getBody().message());
}
@Test
void handleError_methodNotAllowed_returnsStatus405() {
when(mockRequest.getAttribute(RequestDispatcher.ERROR_STATUS_CODE)).thenReturn(405);
when(mockRequest.getAttribute(RequestDispatcher.ERROR_MESSAGE)).thenReturn(null);
final ResponseEntity<ErrorResponse> response = controller.handleError(mockRequest);
assertEquals(HttpStatus.METHOD_NOT_ALLOWED, response.getStatusCode());
assertNotNull(response.getBody());
assertEquals("Method not allowed", response.getBody().message());
}
@Test
void handleError_unsupportedMediaType_returnsStatus415() {
when(mockRequest.getAttribute(RequestDispatcher.ERROR_STATUS_CODE)).thenReturn(415);
when(mockRequest.getAttribute(RequestDispatcher.ERROR_MESSAGE)).thenReturn(null);
final ResponseEntity<ErrorResponse> response = controller.handleError(mockRequest);
assertEquals(HttpStatus.UNSUPPORTED_MEDIA_TYPE, response.getStatusCode());
assertNotNull(response.getBody());
assertEquals("Unsupported media type", response.getBody().message());
}
@Test
void handleError_internalServerError_returnsStatus500() {
when(mockRequest.getAttribute(RequestDispatcher.ERROR_STATUS_CODE)).thenReturn(500);
when(mockRequest.getAttribute(RequestDispatcher.ERROR_MESSAGE)).thenReturn(null);
final ResponseEntity<ErrorResponse> response = controller.handleError(mockRequest);
assertEquals(HttpStatus.INTERNAL_SERVER_ERROR, response.getStatusCode());
assertNotNull(response.getBody());
assertEquals("Internal server error", response.getBody().message());
}
@Test
void handleError_nullStatusCode_defaults500() {
when(mockRequest.getAttribute(RequestDispatcher.ERROR_STATUS_CODE)).thenReturn(null);
when(mockRequest.getAttribute(RequestDispatcher.ERROR_MESSAGE)).thenReturn(null);
final ResponseEntity<ErrorResponse> response = controller.handleError(mockRequest);
assertEquals(HttpStatus.INTERNAL_SERVER_ERROR, response.getStatusCode());
assertNotNull(response.getBody());
// When status is null, HttpStatus.resolve() returns null, which triggers default 500
// but the message comes from the switch default case for INTERNAL_SERVER_ERROR
assertEquals("Internal server error", response.getBody().message());
}
@Test
void handleError_unknownStatusCode_usesReasonPhrase() {
// 418 I'm a teapot - uncommon status with no custom message mapping
when(mockRequest.getAttribute(RequestDispatcher.ERROR_STATUS_CODE)).thenReturn(418);
when(mockRequest.getAttribute(RequestDispatcher.ERROR_MESSAGE)).thenReturn(null);
final ResponseEntity<ErrorResponse> response = controller.handleError(mockRequest);
assertEquals(HttpStatus.I_AM_A_TEAPOT, response.getStatusCode());
assertNotNull(response.getBody());
assertEquals("I'm a teapot", response.getBody().message());
}
@Test
void handleError_invalidStatusCode_defaultsTo500() {
// 999 is not a valid HTTP status code - HttpStatus.resolve() returns null
when(mockRequest.getAttribute(RequestDispatcher.ERROR_STATUS_CODE)).thenReturn(999);
when(mockRequest.getAttribute(RequestDispatcher.ERROR_MESSAGE)).thenReturn(null);
final ResponseEntity<ErrorResponse> response = controller.handleError(mockRequest);
// Falls back to 500 Internal Server Error when status code is unresolvable
assertEquals(HttpStatus.INTERNAL_SERVER_ERROR, response.getStatusCode());
assertNotNull(response.getBody());
assertEquals("An unexpected error occurred", response.getBody().message());
}
@Test
void handleError_withCustomMessage_usesProvidedMessage() {
when(mockRequest.getAttribute(RequestDispatcher.ERROR_STATUS_CODE)).thenReturn(400);
when(mockRequest.getAttribute(RequestDispatcher.ERROR_MESSAGE)).thenReturn("Invalid path variable");
final ResponseEntity<ErrorResponse> response = controller.handleError(mockRequest);
assertEquals(HttpStatus.BAD_REQUEST, response.getStatusCode());
assertNotNull(response.getBody());
assertEquals("Invalid path variable", response.getBody().message());
}
@Test
void handleError_withBlankMessage_usesDefaultMessage() {
when(mockRequest.getAttribute(RequestDispatcher.ERROR_STATUS_CODE)).thenReturn(400);
when(mockRequest.getAttribute(RequestDispatcher.ERROR_MESSAGE)).thenReturn(" ");
final ResponseEntity<ErrorResponse> response = controller.handleError(mockRequest);
assertEquals(HttpStatus.BAD_REQUEST, response.getStatusCode());
assertNotNull(response.getBody());
assertEquals("Bad request", response.getBody().message());
}
@Test
void handleError_withEmptyMessage_usesDefaultMessage() {
when(mockRequest.getAttribute(RequestDispatcher.ERROR_STATUS_CODE)).thenReturn(404);
when(mockRequest.getAttribute(RequestDispatcher.ERROR_MESSAGE)).thenReturn("");
final ResponseEntity<ErrorResponse> response = controller.handleError(mockRequest);
assertEquals(HttpStatus.NOT_FOUND, response.getStatusCode());
assertNotNull(response.getBody());
assertEquals("Resource not found", response.getBody().message());
}
@ParameterizedTest
@CsvSource({
"400, Bad request",
"404, Resource not found",
"405, Method not allowed",
"415, Unsupported media type",
"500, Internal server error"
})
void handleError_statusCodeMapping(final int statusCode, final String expectedMessage) {
when(mockRequest.getAttribute(RequestDispatcher.ERROR_STATUS_CODE)).thenReturn(statusCode);
when(mockRequest.getAttribute(RequestDispatcher.ERROR_MESSAGE)).thenReturn(null);
final ResponseEntity<ErrorResponse> response = controller.handleError(mockRequest);
assertNotNull(response.getBody());
assertEquals(expectedMessage, response.getBody().message());
}
}