Skip to content

Commit d1d9d66

Browse files
committed
build: more unit tests
1 parent d80305b commit d1d9d66

4 files changed

Lines changed: 352 additions & 23 deletions

File tree

jooby/src/main/java/io/jooby/internal/MutedServer.java

Lines changed: 23 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,7 @@
1919

2020
public class MutedServer implements Server {
2121
private Server delegate;
22-
2322
private List<String> mute;
24-
2523
private LoggingService loggingService;
2624

2725
private MutedServer(Server server, LoggingService loggingService, List<String> mute) {
@@ -35,30 +33,32 @@ public OutputFactory getOutputFactory() {
3533
return delegate.getOutputFactory();
3634
}
3735

38-
/**
39-
* Muted a server when need it.
40-
*
41-
* @param server Server to mute.
42-
* @return Muted server or same server.
43-
*/
36+
static Optional<LoggingService> loadLoggingService(ClassLoader classLoader) {
37+
return ServiceLoader.load(LoggingService.class, classLoader).findFirst();
38+
}
39+
4440
public static Server mute(Server server, String... logger) {
45-
if (server instanceof MutedServer) {
41+
var loggingService = loadLoggingService(server.getClass().getClassLoader());
42+
43+
if (loggingService.isEmpty()) {
4644
return server;
4745
}
48-
List<String> mute =
49-
Stream.concat(server.getLoggerOff().stream(), Stream.of(logger))
50-
.collect(Collectors.toList());
51-
52-
Optional<LoggingService> loggingService =
53-
ServiceLoader.load(LoggingService.class, server.getClass().getClassLoader()).findFirst();
54-
return loggingService
55-
.filter(service -> !mute.isEmpty())
56-
.<Server>map(service -> new MutedServer(server, service, mute))
57-
.orElse(server);
46+
47+
Server delegate = server instanceof MutedServer ? ((MutedServer) server).delegate : server;
48+
var existingMute =
49+
server instanceof MutedServer ? ((MutedServer) server).mute : delegate.getLoggerOff();
50+
51+
Stream<String> newLoggers = logger == null ? Stream.empty() : Stream.of(logger);
52+
53+
var mute =
54+
Stream.concat(existingMute.stream(), newLoggers).distinct().collect(Collectors.toList());
55+
56+
return new MutedServer(delegate, loggingService.get(), mute);
5857
}
5958

6059
public Server setOptions(ServerOptions options) {
61-
return delegate.setOptions(options);
60+
delegate.setOptions(options);
61+
return this;
6262
}
6363

6464
public String getName() {
@@ -71,16 +71,16 @@ public ServerOptions getOptions() {
7171

7272
public Server start(Jooby... application) {
7373
loggingService.logOff(mute, () -> delegate.start(application));
74-
return delegate;
74+
return this;
7575
}
7676

7777
public List<String> getLoggerOff() {
78-
return delegate.getLoggerOff();
78+
return mute;
7979
}
8080

8181
public Server stop() {
8282
loggingService.logOff(mute, delegate::stop);
83-
return delegate;
83+
return this;
8484
}
8585

8686
@Override
Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
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.internal;
7+
8+
import static org.junit.jupiter.api.Assertions.assertEquals;
9+
import static org.junit.jupiter.api.Assertions.assertFalse;
10+
import static org.junit.jupiter.api.Assertions.assertThrows;
11+
import static org.junit.jupiter.api.Assertions.assertTrue;
12+
13+
import java.util.List;
14+
import java.util.Locale;
15+
import java.util.Optional;
16+
17+
import org.junit.jupiter.api.DisplayName;
18+
import org.junit.jupiter.api.Test;
19+
20+
public class LocaleUtilsTest {
21+
22+
@Test
23+
@DisplayName("Test parseRanges branches: null, valid sorting, trailing semicolon, and exceptions")
24+
void testParseRanges() {
25+
// 1. Null branch
26+
assertEquals(Optional.empty(), LocaleUtils.parseRanges(null));
27+
28+
// 2. Valid string with mixed weights (Verifies descending sort order branch)
29+
// "en-US" gets default weight 1.0. "en" is 0.9, "es" is 0.8
30+
Optional<List<Locale.LanguageRange>> ranges =
31+
LocaleUtils.parseRanges("es;q=0.8,en-US,en;q=0.9");
32+
assertTrue(ranges.isPresent());
33+
List<Locale.LanguageRange> list = ranges.get();
34+
assertEquals(3, list.size());
35+
assertEquals("en-us", list.get(0).getRange()); // 1.0
36+
assertEquals("en", list.get(1).getRange()); // 0.9
37+
assertEquals("es", list.get(2).getRange()); // 0.8
38+
39+
// 3. Trailing semicolon branch (ends with ';')
40+
Optional<List<Locale.LanguageRange>> trailing = LocaleUtils.parseRanges("en-US;q=0.8;");
41+
assertTrue(trailing.isPresent());
42+
assertEquals(1, trailing.get().size());
43+
assertEquals("en-us", trailing.get().get(0).getRange());
44+
assertEquals(0.8, trailing.get().get(0).getWeight());
45+
46+
// 4. Exception catch branch (IllegalArgumentException -> Optional.empty)
47+
// Triggers exception natively via weight > 1.0 (Valid weights are 0.0 to 1.0)
48+
assertEquals(Optional.empty(), LocaleUtils.parseRanges("en;q=2.0"));
49+
50+
// (Note: Removed the "a b c" assertion as Java's parser unexpectedly strips spaces and accepts
51+
// it)
52+
}
53+
54+
@Test
55+
@DisplayName("Test parseLocales mappings")
56+
void testParseLocales() {
57+
// Valid branch
58+
Optional<List<Locale>> locales = LocaleUtils.parseLocales("es-AR,en-US;q=0.8");
59+
assertTrue(locales.isPresent());
60+
assertEquals(2, locales.get().size());
61+
62+
// Verifies the map sequence translates LanguageRanges into Locale objects
63+
assertEquals(Locale.forLanguageTag("es-AR"), locales.get().get(0));
64+
assertEquals(Locale.forLanguageTag("en-US"), locales.get().get(1));
65+
66+
// Invalid branch (propagates Optional.empty correctly using an invalid weight)
67+
assertFalse(LocaleUtils.parseLocales("en;q=invalid").isPresent());
68+
}
69+
70+
@Test
71+
@DisplayName("Test parseLocalesOrFail success and custom exception branches")
72+
void testParseLocalesOrFail() {
73+
// Valid branch
74+
List<Locale> locales = LocaleUtils.parseLocalesOrFail("es-AR");
75+
assertEquals(1, locales.size());
76+
assertEquals(Locale.forLanguageTag("es-AR"), locales.get(0));
77+
78+
// Exception branch (.orElseThrow)
79+
IllegalArgumentException ex =
80+
assertThrows(
81+
IllegalArgumentException.class,
82+
() -> {
83+
LocaleUtils.parseLocalesOrFail("en;q=invalid");
84+
});
85+
86+
// Verifies the custom formatted message was constructed correctly
87+
assertTrue(ex.getMessage().contains("Invalid value 'en;q=invalid'"));
88+
assertTrue(ex.getMessage().contains("java.util.Locale$LanguageRange"));
89+
}
90+
}
Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
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.internal;
7+
8+
import static org.junit.jupiter.api.Assertions.assertEquals;
9+
import static org.junit.jupiter.api.Assertions.assertSame;
10+
import static org.junit.jupiter.api.Assertions.assertTrue;
11+
import static org.mockito.ArgumentMatchers.*;
12+
import static org.mockito.Mockito.CALLS_REAL_METHODS;
13+
import static org.mockito.Mockito.doAnswer;
14+
import static org.mockito.Mockito.mock;
15+
import static org.mockito.Mockito.mockStatic;
16+
import static org.mockito.Mockito.verify;
17+
import static org.mockito.Mockito.when;
18+
19+
import java.util.Collections;
20+
import java.util.List;
21+
import java.util.Optional;
22+
23+
import org.junit.jupiter.api.DisplayName;
24+
import org.junit.jupiter.api.Test;
25+
import org.mockito.MockedStatic;
26+
27+
import io.jooby.*;
28+
import io.jooby.output.OutputFactory;
29+
30+
public class MutedServerTest {
31+
32+
@Test
33+
@DisplayName("Test early-exit muting logic, logger aggregation, and null vararg safety")
34+
void testMutedServer() {
35+
Server delegate = mock(Server.class);
36+
when(delegate.getLoggerOff()).thenReturn(Collections.singletonList("base.logger"));
37+
when(delegate.getName()).thenReturn("mock-server");
38+
when(delegate.toString()).thenReturn("MockServer");
39+
40+
ServerOptions options = new ServerOptions();
41+
when(delegate.getOptions()).thenReturn(options);
42+
OutputFactory outFactory = mock(OutputFactory.class);
43+
when(delegate.getOutputFactory()).thenReturn(outFactory);
44+
45+
LoggingService loggingService = mock(LoggingService.class);
46+
47+
// Mock logOff to execute the lambda synchronously
48+
doAnswer(
49+
inv -> {
50+
Runnable action = inv.getArgument(1);
51+
action.run();
52+
return null;
53+
})
54+
.when(loggingService)
55+
.logOff(anyList(), any(SneakyThrows.Runnable.class));
56+
57+
Jooby app = new Jooby();
58+
59+
// MOCK MutedServer.class INSTEAD OF ServiceLoader
60+
try (MockedStatic<MutedServer> mockedServer =
61+
mockStatic(MutedServer.class, CALLS_REAL_METHODS)) {
62+
// Force our extracted method to return the mocked logging service
63+
mockedServer
64+
.when(() -> MutedServer.loadLoggingService(any()))
65+
.thenReturn(Optional.of(loggingService));
66+
67+
// 1. Create MutedServer
68+
Server muted1 = MutedServer.mute(delegate, "new.logger");
69+
assertTrue(muted1 instanceof MutedServer, "Should be wrapped in MutedServer");
70+
71+
// 2. Test Chaining MutedServer & Null Varargs Safety
72+
Server muted2 = MutedServer.mute(muted1, (String[]) null);
73+
muted2 = MutedServer.mute(muted2, "another.logger");
74+
assertTrue(muted2 instanceof MutedServer);
75+
76+
// Verify simple delegates
77+
assertEquals("mock-server", muted2.getName());
78+
assertSame(options, muted2.getOptions());
79+
assertSame(outFactory, muted2.getOutputFactory());
80+
assertEquals("MockServer", muted2.toString());
81+
82+
// Verify merged loggers
83+
List<String> combinedMute = muted2.getLoggerOff();
84+
assertTrue(combinedMute.contains("base.logger"));
85+
assertTrue(combinedMute.contains("new.logger"));
86+
assertTrue(combinedMute.contains("another.logger"));
87+
88+
// 3. Verify Fluent Fixes
89+
ServerOptions newOpts = new ServerOptions();
90+
assertSame(muted2, muted2.setOptions(newOpts));
91+
verify(delegate).setOptions(newOpts);
92+
93+
assertSame(muted2, muted2.start(app));
94+
verify(delegate).start(app);
95+
verify(loggingService).logOff(eq(combinedMute), any(SneakyThrows.Runnable.class));
96+
97+
assertSame(muted2, muted2.stop());
98+
verify(delegate).stop();
99+
}
100+
}
101+
102+
@Test
103+
@DisplayName("Test early exit when no LoggingService is present")
104+
void testMuteEarlyExitNoLoggingService() {
105+
Server delegate = mock(Server.class);
106+
107+
// MOCK MutedServer.class
108+
try (MockedStatic<MutedServer> mockedServer =
109+
mockStatic(MutedServer.class, CALLS_REAL_METHODS)) {
110+
// Force it to return empty, triggering the if (loggingService.isEmpty()) branch
111+
mockedServer.when(() -> MutedServer.loadLoggingService(any())).thenReturn(Optional.empty());
112+
113+
Server unmuted = MutedServer.mute(delegate, "some.logger");
114+
115+
// Asserts that the original delegate is returned untouched
116+
assertSame(delegate, unmuted);
117+
}
118+
}
119+
}

0 commit comments

Comments
 (0)