Skip to content

Commit 06df47c

Browse files
authored
Improve test coverage (#99)
1 parent 522dbcd commit 06df47c

8 files changed

Lines changed: 755 additions & 5 deletions

src/test/java/ee/bitweb/core/api/model/exception/ControllerAdvisorIntegrationTests.java

Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -427,6 +427,133 @@ void testDateFieldMethodArgumentNotValidException() throws Exception {
427427
assertIdField(result);
428428
}
429429

430+
@Test
431+
@DisplayName("BindException: Should return validation errors for missing required form fields")
432+
void onBindExceptionWithMissingRequiredFieldsShouldReturnBadRequest() throws Exception {
433+
MockHttpServletRequestBuilder mockMvcBuilder = get(TestPingController.BASE_URL + "/validated-complex")
434+
.header(TRACE_ID_HEADER_NAME, "1234567890");
435+
436+
ResultActions result = mockMvc.perform(mockMvcBuilder).andDo(print());
437+
ResponseAssertions.assertValidationErrorResponse(
438+
result,
439+
List.of(
440+
Error.notNull("age"),
441+
Error.notBlank("email"),
442+
Error.notBlank("name"),
443+
Error.notNull("name")
444+
)
445+
);
446+
assertIdField(result);
447+
}
448+
449+
@Test
450+
@DisplayName("BindException: Should return validation errors for blank fields")
451+
void onBindExceptionWithBlankFieldsShouldReturnBadRequest() throws Exception {
452+
MockHttpServletRequestBuilder mockMvcBuilder = get(TestPingController.BASE_URL + "/validated-complex")
453+
.header(TRACE_ID_HEADER_NAME, "1234567890")
454+
.param("name", "")
455+
.param("age", "25")
456+
.param("email", " ");
457+
458+
ResultActions result = mockMvc.perform(mockMvcBuilder).andDo(print());
459+
ResponseAssertions.assertValidationErrorResponse(
460+
result,
461+
List.of(
462+
Error.notBlank("email"),
463+
Error.notBlank("name")
464+
)
465+
);
466+
assertIdField(result);
467+
}
468+
469+
@Test
470+
@DisplayName("BindException: Should return type mismatch error for invalid integer")
471+
void onBindExceptionWithTypeMismatchShouldReturnBadRequest() throws Exception {
472+
MockHttpServletRequestBuilder mockMvcBuilder = get(TestPingController.BASE_URL + "/validated-complex")
473+
.header(TRACE_ID_HEADER_NAME, "1234567890")
474+
.param("name", "John")
475+
.param("age", "not-a-number")
476+
.param("email", "john@example.com");
477+
478+
ResultActions result = mockMvc.perform(mockMvcBuilder).andDo(print());
479+
ResponseAssertions.assertValidationErrorResponse(
480+
result,
481+
List.of(
482+
new Error("age", "typeMismatch", "Unable to interpret value: not-a-number")
483+
)
484+
);
485+
assertIdField(result);
486+
}
487+
488+
@Test
489+
@DisplayName("BindException: Should return multiple errors for type mismatch and validation")
490+
void onBindExceptionWithMixedErrorsShouldReturnBadRequest() throws Exception {
491+
MockHttpServletRequestBuilder mockMvcBuilder = get(TestPingController.BASE_URL + "/validated-complex")
492+
.header(TRACE_ID_HEADER_NAME, "1234567890")
493+
.param("name", "")
494+
.param("age", "invalid")
495+
.param("email", "test@example.com");
496+
497+
ResultActions result = mockMvc.perform(mockMvcBuilder).andDo(print());
498+
ResponseAssertions.assertValidationErrorResponse(
499+
result,
500+
List.of(
501+
new Error("age", "typeMismatch", "Unable to interpret value: invalid"),
502+
Error.notBlank("name")
503+
)
504+
);
505+
assertIdField(result);
506+
}
507+
508+
@Test
509+
@DisplayName("BindException: Should handle type mismatch on form binding without @Valid")
510+
void onPureBindExceptionWithTypeMismatchShouldReturnBadRequest() throws Exception {
511+
MockHttpServletRequestBuilder mockMvcBuilder = get(TestPingController.BASE_URL + "/form-binding")
512+
.header(TRACE_ID_HEADER_NAME, "1234567890")
513+
.param("name", "John")
514+
.param("count", "not-a-number");
515+
516+
ResultActions result = mockMvc.perform(mockMvcBuilder).andDo(print());
517+
ResponseAssertions.assertValidationErrorResponse(
518+
result,
519+
List.of(
520+
new Error("count", "typeMismatch", "Unable to interpret value: not-a-number")
521+
)
522+
);
523+
assertIdField(result);
524+
}
525+
526+
@Test
527+
@DisplayName("BindException: Should handle multiple type mismatches on form binding without @Valid")
528+
void onPureBindExceptionWithMultipleTypeMismatchesShouldReturnBadRequest() throws Exception {
529+
MockHttpServletRequestBuilder mockMvcBuilder = get(TestPingController.BASE_URL + "/form-binding")
530+
.header(TRACE_ID_HEADER_NAME, "1234567890")
531+
.param("count", "abc")
532+
.param("date", "invalid-date");
533+
534+
ResultActions result = mockMvc.perform(mockMvcBuilder).andDo(print());
535+
ResponseAssertions.assertValidationErrorResponse(
536+
result,
537+
List.of(
538+
new Error("count", "typeMismatch", "Unable to interpret value: abc"),
539+
new Error("date", "typeMismatch", "Unable to interpret value: invalid-date")
540+
)
541+
);
542+
assertIdField(result);
543+
}
544+
545+
@Test
546+
@DisplayName("BindException: Should handle explicitly thrown BindException")
547+
void onExplicitBindExceptionShouldReturnBadRequest() throws Exception {
548+
MockHttpServletRequestBuilder mockMvcBuilder = get(TestPingController.BASE_URL + "/throw-bind-exception")
549+
.header(TRACE_ID_HEADER_NAME, "1234567890");
550+
551+
ResultActions result = mockMvc.perform(mockMvcBuilder).andDo(print());
552+
result.andExpect(status().isBadRequest())
553+
.andExpect(jsonPath("$.id", is("1234567890_generated-trace-id")))
554+
.andExpect(jsonPath("$.message", is("INVALID_ARGUMENT")));
555+
}
556+
430557
private void testFieldPost(String field, String value, String expectedReason, String expectedMessage) throws Exception {
431558
MockHttpServletRequestBuilder mockMvcBuilder = post(TestPingController.BASE_URL)
432559
.header(TRACE_ID_HEADER_NAME, "1234567890")
Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
1+
package ee.bitweb.core.api.model.exception;
2+
3+
import ee.bitweb.core.exception.persistence.Criteria;
4+
import org.junit.jupiter.api.DisplayName;
5+
import org.junit.jupiter.api.Tag;
6+
import org.junit.jupiter.api.Test;
7+
import org.junit.jupiter.params.ParameterizedTest;
8+
import org.junit.jupiter.params.provider.Arguments;
9+
import org.junit.jupiter.params.provider.MethodSource;
10+
11+
import java.util.stream.Stream;
12+
13+
import static org.junit.jupiter.api.Assertions.*;
14+
15+
@Tag("unit")
16+
class CriteriaResponseTest {
17+
18+
@Test
19+
@DisplayName("Should create from Criteria object")
20+
void shouldCreateFromCriteriaObject() {
21+
Criteria criteria = new Criteria("id", "123");
22+
23+
CriteriaResponse response = new CriteriaResponse(criteria);
24+
25+
assertAll(
26+
() -> assertEquals("id", response.getField()),
27+
() -> assertEquals("123", response.getValue())
28+
);
29+
}
30+
31+
@Test
32+
@DisplayName("Should create with direct parameters")
33+
void shouldCreateWithDirectParameters() {
34+
CriteriaResponse response = new CriteriaResponse("email", "test@example.com");
35+
36+
assertAll(
37+
() -> assertEquals("email", response.getField()),
38+
() -> assertEquals("test@example.com", response.getValue())
39+
);
40+
}
41+
42+
static Stream<Arguments> comparisonCases() {
43+
return Stream.of(
44+
Arguments.of("alpha", "1", "beta", "1", -1, "field alpha < beta"),
45+
Arguments.of("beta", "1", "alpha", "1", 1, "field beta > alpha"),
46+
Arguments.of("field", "1", "field", "1", 0, "equal fields and values"),
47+
Arguments.of("field", "aaa", "field", "bbb", -1, "same field, value aaa < bbb"),
48+
Arguments.of("field", "bbb", "field", "aaa", 1, "same field, value bbb > aaa")
49+
);
50+
}
51+
52+
@ParameterizedTest(name = "compareTo: {5}")
53+
@MethodSource("comparisonCases")
54+
void shouldCompareCorrectly(String field1, String value1, String field2, String value2,
55+
int expectedSign, String description) {
56+
CriteriaResponse first = new CriteriaResponse(field1, value1);
57+
CriteriaResponse second = new CriteriaResponse(field2, value2);
58+
59+
int result = first.compareTo(second);
60+
61+
assertEquals(expectedSign, Integer.signum(result));
62+
}
63+
64+
@Test
65+
@DisplayName("Should compare case-insensitively")
66+
void shouldCompareCaseInsensitively() {
67+
CriteriaResponse lower = new CriteriaResponse("email", "value");
68+
CriteriaResponse upper = new CriteriaResponse("EMAIL", "VALUE");
69+
70+
assertEquals(0, lower.compareTo(upper));
71+
}
72+
73+
@Test
74+
@DisplayName("Should handle null field - nulls come first")
75+
void shouldHandleNullFieldNullsFirst() {
76+
CriteriaResponse withNull = new CriteriaResponse(null, "value");
77+
CriteriaResponse withValue = new CriteriaResponse("field", "value");
78+
79+
assertTrue(withNull.compareTo(withValue) < 0);
80+
assertTrue(withValue.compareTo(withNull) > 0);
81+
}
82+
83+
@Test
84+
@DisplayName("Should handle null value - nulls come first")
85+
void shouldHandleNullValueNullsFirst() {
86+
CriteriaResponse withNull = new CriteriaResponse("field", null);
87+
CriteriaResponse withValue = new CriteriaResponse("field", "value");
88+
89+
assertTrue(withNull.compareTo(withValue) < 0);
90+
assertTrue(withValue.compareTo(withNull) > 0);
91+
}
92+
93+
@Test
94+
@DisplayName("Should handle both null fields")
95+
void shouldHandleBothNullFields() {
96+
CriteriaResponse first = new CriteriaResponse(null, "a");
97+
CriteriaResponse second = new CriteriaResponse(null, "b");
98+
99+
assertTrue(first.compareTo(second) < 0);
100+
}
101+
102+
@Test
103+
@DisplayName("Should handle comparison with null object")
104+
void shouldHandleComparisonWithNullObject() {
105+
CriteriaResponse response = new CriteriaResponse("field", "value");
106+
107+
assertTrue(response.compareTo(null) > 0);
108+
}
109+
110+
@Test
111+
@DisplayName("Should be equal when field and value match")
112+
void shouldBeEqualWhenFieldAndValueMatch() {
113+
CriteriaResponse first = new CriteriaResponse("field", "value");
114+
CriteriaResponse second = new CriteriaResponse("field", "value");
115+
116+
assertEquals(first, second);
117+
assertEquals(first.hashCode(), second.hashCode());
118+
}
119+
120+
@Test
121+
@DisplayName("Should not be equal when field differs")
122+
void shouldNotBeEqualWhenFieldDiffers() {
123+
CriteriaResponse first = new CriteriaResponse("field1", "value");
124+
CriteriaResponse second = new CriteriaResponse("field2", "value");
125+
126+
assertNotEquals(first, second);
127+
}
128+
129+
@Test
130+
@DisplayName("Should not be equal when value differs")
131+
void shouldNotBeEqualWhenValueDiffers() {
132+
CriteriaResponse first = new CriteriaResponse("field", "value1");
133+
CriteriaResponse second = new CriteriaResponse("field", "value2");
134+
135+
assertNotEquals(first, second);
136+
}
137+
}

0 commit comments

Comments
 (0)