Restructuring and more tests

This commit is contained in:
Ryan Harg 2024-11-27 11:20:54 +01:00
parent 4b0f3cefb8
commit 3473cf54d6
5 changed files with 124 additions and 29 deletions

View file

@ -3,7 +3,7 @@ package de.rpr.ddnsclient;
import de.rpr.ddnsclient.dyndns.DynDns; import de.rpr.ddnsclient.dyndns.DynDns;
import de.rpr.ddnsclient.dyndns.DynDnsRouter; import de.rpr.ddnsclient.dyndns.DynDnsRouter;
import de.rpr.ddnsclient.lookup.DnsResolver; import de.rpr.ddnsclient.lookup.DnsResolver;
import de.rpr.ddnsclient.lookup.IpProvider; import de.rpr.ddnsclient.lookup.PublicIpLookup;
import de.rpr.ddnsclient.model.Config; import de.rpr.ddnsclient.model.Config;
import de.rpr.ddnsclient.model.DyndnsAuth; import de.rpr.ddnsclient.model.DyndnsAuth;
import de.rpr.ddnsclient.model.IPs; import de.rpr.ddnsclient.model.IPs;
@ -23,7 +23,7 @@ public class Updater {
private static final Logger log = Logger.getLogger(Updater.class); private static final Logger log = Logger.getLogger(Updater.class);
private final DynDnsRouter dynDnsRouter; private final DynDnsRouter dynDnsRouter;
private final IpProvider ipProvider; private final PublicIpLookup publicIpLookup;
private final DnsResolver dnsResolver; private final DnsResolver dnsResolver;
private final Duration backoff; private final Duration backoff;
@ -33,12 +33,12 @@ public class Updater {
@Inject @Inject
public Updater(DynDnsRouter dynDnsRouter, public Updater(DynDnsRouter dynDnsRouter,
IpProvider ipProvider, PublicIpLookup publicIpLookup,
DnsResolver dnsResolver, DnsResolver dnsResolver,
Config config, Config config,
@ConfigProperty(name = "ddnsclient.backoff-duration:300s") Duration backoff) { @ConfigProperty(name = "ddnsclient.backoff-duration:300s") Duration backoff) {
this.dynDnsRouter = dynDnsRouter; this.dynDnsRouter = dynDnsRouter;
this.ipProvider = ipProvider; this.publicIpLookup = publicIpLookup;
this.dnsResolver = dnsResolver; this.dnsResolver = dnsResolver;
this.config = config; this.config = config;
this.backoff = backoff; this.backoff = backoff;
@ -51,7 +51,7 @@ public class Updater {
throw new IllegalStateException("Missing configuration"); throw new IllegalStateException("Missing configuration");
} }
IPs publicIps = ipProvider.getPublicIps(); IPs publicIps = publicIpLookup.get();
log.debugf("Public ips - v4: %s, v6: %s", publicIps.v4(), publicIps.v6()); log.debugf("Public ips - v4: %s, v6: %s", publicIps.v4(), publicIps.v6());
config.forEach(cfg -> { config.forEach(cfg -> {

View file

@ -0,0 +1,20 @@
package de.rpr.ddnsclient.lookup;
import jakarta.enterprise.context.ApplicationScoped;
@ApplicationScoped
class CurlProcessFactory {
enum IpClass {
V4,
V6
}
ProcessBuilder create(String provider, IpClass ipClass) {
if (ipClass == IpClass.V4) {
return new ProcessBuilder("curl", "--ipv4", provider);
} else {
return new ProcessBuilder("curl", "--ipv6", provider);
}
}
}

View file

@ -17,9 +17,20 @@ import java.util.NoSuchElementException;
import java.util.Optional; import java.util.Optional;
@ApplicationScoped @ApplicationScoped
public class IpProvider { public class PublicIpLookup {
private static final Logger log = Logger.getLogger(IpProvider.class); private static final Logger log = Logger.getLogger(PublicIpLookup.class);
private final CurlProcessFactory curlProcessFactory;
private final Optional<String> configuredProvider;
PublicIpLookup(
CurlProcessFactory curlProcessFactory,
@ConfigProperty(name = "ddnsclient.ip-provider") Optional<String> configuredProvider
) {
this.curlProcessFactory = curlProcessFactory;
this.configuredProvider = configuredProvider;
}
enum Providers { enum Providers {
IFCONFIG_ME("ifconfig.me", "ifconfig.me"), IFCONFIG_ME("ifconfig.me", "ifconfig.me"),
@ -50,9 +61,6 @@ public class IpProvider {
} }
} }
@ConfigProperty(name = "ddnsclient.ip-provider")
Optional<String> configuredProvider;
private Optional<Providers> provider = Optional.empty(); private Optional<Providers> provider = Optional.empty();
void onStart(@Observes StartupEvent event) { void onStart(@Observes StartupEvent event) {
@ -63,11 +71,11 @@ public class IpProvider {
} }
); );
} catch (Exception e) { } catch (Exception e) {
Quarkus.asyncExit(1); throw new IllegalStateException("Unknow provider configured!", e);
} }
} }
public IPs getPublicIps() { public IPs get() {
log.trace("Retrieving public ips."); log.trace("Retrieving public ips.");
try { try {
@ -76,9 +84,9 @@ public class IpProvider {
IPs.Builder ipsBuilder = new IPs.Builder(); IPs.Builder ipsBuilder = new IPs.Builder();
try { try {
Process curl = new ProcessBuilder("curl", "--ipv4", provider).start(); Process curlProcess = curlProcessFactory.create(provider, CurlProcessFactory.IpClass.V4).start();
curl.waitFor(); curlProcess.waitFor();
InputStream inputStream = curl.getInputStream(); InputStream inputStream = curlProcess.getInputStream();
BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream)); BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream));
String v4 = reader.readLine(); String v4 = reader.readLine();
log.tracef("Retrieved ipv4: %s", v4); log.tracef("Retrieved ipv4: %s", v4);
@ -88,9 +96,9 @@ public class IpProvider {
} }
try { try {
Process curl = new ProcessBuilder("curl", "--ipv6", provider).start(); Process curlProcess = curlProcessFactory.create(provider, CurlProcessFactory.IpClass.V6).start();
curl.waitFor(); curlProcess.waitFor();
InputStream inputStream = curl.getInputStream(); InputStream inputStream = curlProcess.getInputStream();
BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream)); BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream));
String v6 = reader.readLine(); String v6 = reader.readLine();
log.tracef("Retrieved ipv6: %s", v6); log.tracef("Retrieved ipv6: %s", v6);

View file

@ -4,7 +4,7 @@ import de.rpr.ddnsclient.dyndns.Ddnss;
import de.rpr.ddnsclient.dyndns.DynDns; import de.rpr.ddnsclient.dyndns.DynDns;
import de.rpr.ddnsclient.dyndns.DynDnsRouter; import de.rpr.ddnsclient.dyndns.DynDnsRouter;
import de.rpr.ddnsclient.lookup.DnsResolver; import de.rpr.ddnsclient.lookup.DnsResolver;
import de.rpr.ddnsclient.lookup.IpProvider; import de.rpr.ddnsclient.lookup.PublicIpLookup;
import de.rpr.ddnsclient.model.Config; import de.rpr.ddnsclient.model.Config;
import de.rpr.ddnsclient.model.DyndnsAuth; import de.rpr.ddnsclient.model.DyndnsAuth;
import de.rpr.ddnsclient.model.IPs; import de.rpr.ddnsclient.model.IPs;
@ -27,7 +27,7 @@ class UpdaterTest {
@Mock @Mock
DynDnsRouter dynDnsRouter; DynDnsRouter dynDnsRouter;
@Mock @Mock
IpProvider ipProvider; PublicIpLookup publicIpLookup;
@Mock @Mock
DnsResolver dnsResolver; DnsResolver dnsResolver;
@ -40,7 +40,7 @@ class UpdaterTest {
@Test @Test
void should_throw_exception_if_config_is_empty() { void should_throw_exception_if_config_is_empty() {
Config config = new Config(); Config config = new Config();
Updater updater = new Updater(dynDnsRouter, ipProvider, dnsResolver, config, backoff); Updater updater = new Updater(dynDnsRouter, publicIpLookup, dnsResolver, config, backoff);
Assertions.assertThrows(IllegalStateException.class, updater::run); Assertions.assertThrows(IllegalStateException.class, updater::run);
} }
@ -48,7 +48,7 @@ class UpdaterTest {
void should_process_all_config_entries() { void should_process_all_config_entries() {
IPs ips = new IPs("ipv4", "ipv6"); IPs ips = new IPs("ipv4", "ipv6");
when(ipProvider.getPublicIps()).thenReturn(ips); when(publicIpLookup.get()).thenReturn(ips);
when(dnsResolver.resolve(any())).thenReturn(ips); when(dnsResolver.resolve(any())).thenReturn(ips);
Config multipleConfigEntries = new Config() {{ Config multipleConfigEntries = new Config() {{
@ -56,7 +56,7 @@ class UpdaterTest {
add(new Value("ddnss", "host2.example.org", null, null, "token")); add(new Value("ddnss", "host2.example.org", null, null, "token"));
}}; }};
Updater updater = new Updater(dynDnsRouter, ipProvider, dnsResolver, multipleConfigEntries, backoff); Updater updater = new Updater(dynDnsRouter, publicIpLookup, dnsResolver, multipleConfigEntries, backoff);
updater.run(); updater.run();
ArgumentCaptor<String> hostnameCaptor = ArgumentCaptor.forClass(String.class); ArgumentCaptor<String> hostnameCaptor = ArgumentCaptor.forClass(String.class);
@ -71,10 +71,10 @@ class UpdaterTest {
IPs ips = new IPs("ipv4", "ipv6"); IPs ips = new IPs("ipv4", "ipv6");
when(ipProvider.getPublicIps()).thenReturn(ips); when(publicIpLookup.get()).thenReturn(ips);
when(dnsResolver.resolve("example.org")).thenReturn(ips); when(dnsResolver.resolve("example.org")).thenReturn(ips);
Updater updater = new Updater(dynDnsRouter, ipProvider, dnsResolver, ddnssConfig, backoff); Updater updater = new Updater(dynDnsRouter, publicIpLookup, dnsResolver, ddnssConfig, backoff);
updater.run(); updater.run();
verify(dynDnsRouter, never()).get(any()); verify(dynDnsRouter, never()).get(any());
@ -89,10 +89,10 @@ class UpdaterTest {
when(dynDnsRouter.get("ddnss")).thenReturn(ddnss); when(dynDnsRouter.get("ddnss")).thenReturn(ddnss);
IPs publicIps = new IPs("ipv4", "ipv6"); IPs publicIps = new IPs("ipv4", "ipv6");
when(ipProvider.getPublicIps()).thenReturn(publicIps); when(publicIpLookup.get()).thenReturn(publicIps);
when(dnsResolver.resolve("example.org")).thenReturn(new IPs("registered_ipv4", "registered_ipv6")); when(dnsResolver.resolve("example.org")).thenReturn(new IPs("registered_ipv4", "registered_ipv6"));
Updater updater = new Updater(dynDnsRouter, ipProvider, dnsResolver, ddnssConfig, backoff); Updater updater = new Updater(dynDnsRouter, publicIpLookup, dnsResolver, ddnssConfig, backoff);
updater.run(); updater.run();
verify(ddnss).update( verify(ddnss).update(
@ -110,10 +110,10 @@ class UpdaterTest {
when(dynDnsRouter.get("ddnss")).thenReturn(ddnss); when(dynDnsRouter.get("ddnss")).thenReturn(ddnss);
IPs publicIps = new IPs("ipv4", "ipv6"); IPs publicIps = new IPs("ipv4", "ipv6");
when(ipProvider.getPublicIps()).thenReturn(publicIps); when(publicIpLookup.get()).thenReturn(publicIps);
when(dnsResolver.resolve("example.org")).thenReturn(new IPs("registered_ipv4", "registered_ipv6")); when(dnsResolver.resolve("example.org")).thenReturn(new IPs("registered_ipv4", "registered_ipv6"));
Updater updater = new Updater(dynDnsRouter, ipProvider, dnsResolver, ddnssConfig, backoff); Updater updater = new Updater(dynDnsRouter, publicIpLookup, dnsResolver, ddnssConfig, backoff);
updater.run(); updater.run();
updater.run(); updater.run();

View file

@ -0,0 +1,67 @@
package de.rpr.ddnsclient.lookup;
import de.rpr.ddnsclient.model.IPs;
import io.quarkus.runtime.StartupEvent;
import org.apache.commons.io.IOUtils;
import org.assertj.core.api.Assertions;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.mockito.invocation.InvocationOnMock;
import org.mockito.junit.jupiter.MockitoExtension;
import org.mockito.stubbing.Answer;
import java.util.Optional;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.*;
@ExtendWith(MockitoExtension.class)
class PublicIpLookupTest {
@Mock
CurlProcessFactory curlProcessFactory;
@Test
void unknown_provider_should_throw_exception() {
PublicIpLookup lookup = new PublicIpLookup(curlProcessFactory, Optional.of("unknown"));
Assertions.assertThatExceptionOfType(IllegalStateException.class)
.isThrownBy(() -> lookup.onStart(new StartupEvent()));
}
@Test
void valid_provider_should_be_configurable() {
PublicIpLookup lookup = new PublicIpLookup(curlProcessFactory, Optional.of("ifconfig.me"));
lookup.onStart(new StartupEvent());
}
@Test
void should_use_configured_provider_for_lookups() {
Process v4CurlProcess = mock(Process.class);
when(v4CurlProcess.getInputStream()).thenReturn(IOUtils.toInputStream("ipv4", "UTF-8"));
ProcessBuilder v4CurlProcessBuilder = mock(ProcessBuilder.class, invocationOnMock -> v4CurlProcess);
when(curlProcessFactory.create(any(), eq(CurlProcessFactory.IpClass.V4))).thenReturn(v4CurlProcessBuilder);
Process v6CurlProcess = mock(Process.class);
when(v6CurlProcess.getInputStream()).thenReturn(IOUtils.toInputStream("ipv6", "UTF-8"));
ProcessBuilder v6CurlProcessBuilder = mock(ProcessBuilder.class, invocationOnMock -> v6CurlProcess);
when(curlProcessFactory.create(any(), eq(CurlProcessFactory.IpClass.V6))).thenReturn(v6CurlProcessBuilder);
PublicIpLookup lookup = new PublicIpLookup(curlProcessFactory, Optional.of("ifconfig.me"));
lookup.onStart(new StartupEvent());
IPs result = lookup.get();
assertThat(result).isEqualTo(new IPs("ipv4", "ipv6"));
ArgumentCaptor<String> providerCaptor = ArgumentCaptor.forClass(String.class);
verify(curlProcessFactory, times(2)).create(providerCaptor.capture(), any());
assertThat(providerCaptor.getAllValues()).containsExactly("ifconfig.me", "ifconfig.me");
}
}