Skip to content

Commit 4aa4f94

Browse files
committed
build: add unit test for trpc/jsonrpc-jackson2/avaje-validator
1 parent 59fa0c7 commit 4aa4f94

11 files changed

Lines changed: 1115 additions & 48 deletions

File tree

modules/jooby-avaje-validator/pom.xml

Lines changed: 4 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -42,25 +42,6 @@
4242
<artifactId>jakarta.validation-api</artifactId>
4343
</dependency>
4444

45-
<!-- Test dependencies -->
46-
<dependency>
47-
<groupId>io.jooby</groupId>
48-
<artifactId>jooby-netty</artifactId>
49-
<scope>test</scope>
50-
</dependency>
51-
52-
<dependency>
53-
<groupId>io.jooby</groupId>
54-
<artifactId>jooby-apt</artifactId>
55-
<scope>test</scope>
56-
</dependency>
57-
58-
<dependency>
59-
<groupId>io.jooby</groupId>
60-
<artifactId>jooby-jackson</artifactId>
61-
<scope>test</scope>
62-
</dependency>
63-
6445
<dependency>
6546
<groupId>org.junit.jupiter</groupId>
6647
<artifactId>junit-jupiter-api</artifactId>
@@ -74,21 +55,13 @@
7455
</dependency>
7556

7657
<dependency>
77-
<groupId>io.jooby</groupId>
78-
<artifactId>jooby-test</artifactId>
79-
<scope>test</scope>
80-
</dependency>
81-
82-
<dependency>
83-
<groupId>io.rest-assured</groupId>
84-
<artifactId>rest-assured</artifactId>
58+
<groupId>org.mockito</groupId>
59+
<artifactId>mockito-core</artifactId>
8560
<scope>test</scope>
8661
</dependency>
87-
8862
<dependency>
89-
<groupId>org.assertj</groupId>
90-
<artifactId>assertj-core</artifactId>
91-
<version>3.27.7</version>
63+
<groupId>org.mockito</groupId>
64+
<artifactId>mockito-junit-jupiter</artifactId>
9265
<scope>test</scope>
9366
</dependency>
9467
</dependencies>
Lines changed: 228 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,228 @@
1+
/*
2+
* Jooby https://jooby.io
3+
* Apache License Version 2.0 https://jooby.io/LICENSE.txt
4+
* Copyright 2014 Edgar Espina
5+
*/
6+
package io.jooby.avaje.validator;
7+
8+
import static org.junit.jupiter.api.Assertions.assertNotNull;
9+
import static org.mockito.ArgumentMatchers.any;
10+
import static org.mockito.ArgumentMatchers.anyString;
11+
import static org.mockito.ArgumentMatchers.eq;
12+
import static org.mockito.Mockito.lenient;
13+
import static org.mockito.Mockito.mock;
14+
import static org.mockito.Mockito.mockStatic;
15+
import static org.mockito.Mockito.never;
16+
import static org.mockito.Mockito.verify;
17+
import static org.mockito.Mockito.when;
18+
19+
import java.time.Duration;
20+
import java.time.temporal.ChronoUnit;
21+
import java.util.List;
22+
import java.util.Locale;
23+
24+
import org.junit.jupiter.api.BeforeEach;
25+
import org.junit.jupiter.api.Test;
26+
import org.junit.jupiter.api.extension.ExtendWith;
27+
import org.mockito.Answers;
28+
import org.mockito.ArgumentCaptor;
29+
import org.mockito.Mock;
30+
import org.mockito.MockedStatic;
31+
import org.mockito.junit.jupiter.MockitoExtension;
32+
33+
import com.typesafe.config.Config;
34+
import com.typesafe.config.ConfigValue;
35+
import com.typesafe.config.ConfigValueType;
36+
import io.avaje.validation.Validator;
37+
import io.jooby.Context;
38+
import io.jooby.ErrorHandler;
39+
import io.jooby.Jooby;
40+
import io.jooby.ServiceRegistry;
41+
import io.jooby.StatusCode;
42+
import io.jooby.validation.BeanValidator;
43+
44+
@ExtendWith(MockitoExtension.class)
45+
class AvajeValidatorModuleTest {
46+
47+
@Mock Jooby app;
48+
@Mock ServiceRegistry registry;
49+
@Mock Config config;
50+
51+
@BeforeEach
52+
void setup() {
53+
lenient().when(app.getConfig()).thenReturn(config);
54+
lenient().when(app.getServices()).thenReturn(registry);
55+
lenient().when(app.problemDetailsIsEnabled()).thenReturn(false);
56+
57+
// Explicitly return null to prevent Mockito from returning an empty list,
58+
// which crashes the module's locales.get(0) check.
59+
lenient().when(app.getLocales()).thenReturn(null);
60+
}
61+
62+
@Test
63+
void testInstall_Defaults() {
64+
// Default: No config properties are set
65+
when(config.hasPath(anyString())).thenReturn(false);
66+
67+
try (MockedStatic<Validator> validatorMock = mockStatic(Validator.class)) {
68+
Validator.Builder builder = mock(Validator.Builder.class, Answers.RETURNS_SELF);
69+
Validator validator = mock(Validator.class);
70+
validatorMock.when(Validator::builder).thenReturn(builder);
71+
when(builder.build()).thenReturn(validator);
72+
73+
AvajeValidatorModule module = new AvajeValidatorModule();
74+
module.install(app);
75+
76+
// Verify services are registered
77+
verify(registry).put(Validator.class, validator);
78+
ArgumentCaptor<BeanValidator> beanValidatorCaptor =
79+
ArgumentCaptor.forClass(BeanValidator.class);
80+
verify(registry).put(eq(BeanValidator.class), beanValidatorCaptor.capture());
81+
assertNotNull(beanValidatorCaptor.getValue());
82+
83+
// Verify constraint handler is registered
84+
verify(app).error(any(ConstraintViolationHandler.class));
85+
}
86+
}
87+
88+
@Test
89+
void testInstall_WithConfigStringsAndLocales() {
90+
// Enable all configuration paths
91+
when(config.hasPath(anyString())).thenReturn(true);
92+
93+
// Boolean: failFast
94+
when(config.getBoolean("validation.failFast")).thenReturn(true);
95+
96+
// String: resourcebundle.names
97+
ConfigValue rbNameVal = mock(ConfigValue.class);
98+
when(rbNameVal.valueType()).thenReturn(ConfigValueType.STRING);
99+
when(config.getValue("validation.resourcebundle.names")).thenReturn(rbNameVal);
100+
when(config.getString("validation.resourcebundle.names")).thenReturn("messages");
101+
102+
// Application Locales
103+
when(app.getLocales()).thenReturn(List.of(Locale.US, Locale.UK));
104+
105+
// String: locale.default
106+
when(config.getString("validation.locale.default")).thenReturn("fr-FR");
107+
108+
// String: locale.addedLocales
109+
ConfigValue addedLocalesVal = mock(ConfigValue.class);
110+
when(addedLocalesVal.valueType()).thenReturn(ConfigValueType.STRING);
111+
when(config.getValue("validation.locale.addedLocales")).thenReturn(addedLocalesVal);
112+
when(config.getString("validation.locale.addedLocales")).thenReturn("de-DE");
113+
114+
// Long & String: temporal tolerance and chrono unit
115+
when(config.getLong("validation.temporal.tolerance.value")).thenReturn(100L);
116+
when(config.getString("validation.temporal.tolerance.chronoUnit")).thenReturn("SECONDS");
117+
118+
try (MockedStatic<Validator> validatorMock = mockStatic(Validator.class)) {
119+
Validator.Builder builder = mock(Validator.Builder.class, Answers.RETURNS_SELF);
120+
Validator validator = mock(Validator.class);
121+
validatorMock.when(Validator::builder).thenReturn(builder);
122+
when(builder.build()).thenReturn(validator);
123+
124+
new AvajeValidatorModule().install(app);
125+
126+
// Verify the builder was configured correctly
127+
verify(builder).failFast(true);
128+
verify(builder).addResourceBundles("messages");
129+
130+
// Verification for app.getLocales()
131+
verify(builder).setDefaultLocale(Locale.US);
132+
verify(builder).addLocales(Locale.UK);
133+
134+
// Verification for explicit locale settings
135+
verify(builder).setDefaultLocale(Locale.forLanguageTag("fr-FR"));
136+
verify(builder).addLocales(Locale.forLanguageTag("de-DE"));
137+
138+
// Verification for temporal tolerance
139+
verify(builder).temporalTolerance(Duration.of(100, ChronoUnit.SECONDS));
140+
}
141+
}
142+
143+
@Test
144+
void testInstall_WithConfigListsAndDefaultChronoUnit() {
145+
when(config.hasPath(anyString())).thenReturn(false);
146+
when(config.hasPath("validation.resourcebundle.names")).thenReturn(true);
147+
when(config.hasPath("validation.locale.addedLocales")).thenReturn(true);
148+
when(config.hasPath("validation.temporal.tolerance.value")).thenReturn(true);
149+
150+
// List: resourcebundle.names
151+
ConfigValue rbNameVal = mock(ConfigValue.class);
152+
when(rbNameVal.valueType()).thenReturn(ConfigValueType.LIST);
153+
when(config.getValue("validation.resourcebundle.names")).thenReturn(rbNameVal);
154+
when(config.getStringList("validation.resourcebundle.names"))
155+
.thenReturn(List.of("msg1", "msg2"));
156+
157+
// List: locale.addedLocales
158+
ConfigValue addedLocalesVal = mock(ConfigValue.class);
159+
when(addedLocalesVal.valueType()).thenReturn(ConfigValueType.LIST);
160+
when(config.getValue("validation.locale.addedLocales")).thenReturn(addedLocalesVal);
161+
when(config.getStringList("validation.locale.addedLocales")).thenReturn(List.of("es", "it"));
162+
163+
// Long: temporal tolerance with missing unit (fallback to MILLIS)
164+
when(config.getLong("validation.temporal.tolerance.value")).thenReturn(50L);
165+
166+
try (MockedStatic<Validator> validatorMock = mockStatic(Validator.class)) {
167+
Validator.Builder builder = mock(Validator.Builder.class, Answers.RETURNS_SELF);
168+
Validator validator = mock(Validator.class);
169+
validatorMock.when(Validator::builder).thenReturn(builder);
170+
when(builder.build()).thenReturn(validator);
171+
172+
new AvajeValidatorModule().install(app);
173+
174+
// Verify list unpacking
175+
verify(builder).addResourceBundles("msg1");
176+
verify(builder).addResourceBundles("msg2");
177+
verify(builder).addLocales(Locale.forLanguageTag("es"));
178+
verify(builder).addLocales(Locale.forLanguageTag("it"));
179+
180+
// Verify ChronoUnit defaults to MILLIS
181+
verify(builder).temporalTolerance(Duration.of(50, ChronoUnit.MILLIS));
182+
}
183+
}
184+
185+
@Test
186+
void testInstall_WithModuleBuilderMethodsAndDisabledHandler() {
187+
when(config.hasPath(anyString())).thenReturn(false);
188+
189+
try (MockedStatic<Validator> validatorMock = mockStatic(Validator.class)) {
190+
Validator.Builder builder = mock(Validator.Builder.class, Answers.RETURNS_SELF);
191+
Validator validator = mock(Validator.class);
192+
validatorMock.when(Validator::builder).thenReturn(builder);
193+
when(builder.build()).thenReturn(validator);
194+
195+
AvajeValidatorModule module =
196+
new AvajeValidatorModule()
197+
.doWith(b -> b.failFast(false))
198+
.statusCode(StatusCode.BAD_REQUEST)
199+
.validationTitle("Custom Validation Title")
200+
.logException()
201+
.disableViolationHandler();
202+
203+
module.install(app);
204+
205+
// Verify the custom configurer was called
206+
verify(builder).failFast(false);
207+
208+
// Verify the default violation handler is bypassed completely
209+
verify(app, never()).error(any(ErrorHandler.class));
210+
}
211+
}
212+
213+
@Test
214+
void testBeanValidatorImpl_Validate() {
215+
Validator validator = mock(Validator.class);
216+
Context ctx = mock(Context.class);
217+
when(ctx.locale()).thenReturn(Locale.CANADA);
218+
219+
AvajeValidatorModule.BeanValidatorImpl beanValidator =
220+
new AvajeValidatorModule.BeanValidatorImpl(validator);
221+
222+
Object testBean = new Object();
223+
beanValidator.validate(ctx, testBean);
224+
225+
// Verify it delegates to the underlying Avaje validator with the correct locale
226+
verify(validator).validate(testBean, Locale.CANADA);
227+
}
228+
}

modules/jooby-jsonrpc-jackson2/pom.xml

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,16 @@
2323
<groupId>com.fasterxml.jackson.core</groupId>
2424
<artifactId>jackson-databind</artifactId>
2525
</dependency>
26+
<dependency>
27+
<groupId>org.junit.jupiter</groupId>
28+
<artifactId>junit-jupiter-api</artifactId>
29+
<scope>test</scope>
30+
</dependency>
31+
<dependency>
32+
<groupId>org.mockito</groupId>
33+
<artifactId>mockito-core</artifactId>
34+
<scope>test</scope>
35+
</dependency>
2636

2737
</dependencies>
2838
</project>

0 commit comments

Comments
 (0)