Restructuring and more tests

This commit is contained in:
Ryan Harg 2024-11-27 11:20:54 +01:00
parent 4b0f3cefb8
commit 70a1141750
5 changed files with 139 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.DynDnsRouter;
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.DyndnsAuth;
import de.rpr.ddnsclient.model.IPs;
@ -23,7 +23,7 @@ public class Updater {
private static final Logger log = Logger.getLogger(Updater.class);
private final DynDnsRouter dynDnsRouter;
private final IpProvider ipProvider;
private final PublicIpLookup publicIpLookup;
private final DnsResolver dnsResolver;
private final Duration backoff;
@ -33,12 +33,12 @@ public class Updater {
@Inject
public Updater(DynDnsRouter dynDnsRouter,
IpProvider ipProvider,
PublicIpLookup publicIpLookup,
DnsResolver dnsResolver,
Config config,
@ConfigProperty(name = "ddnsclient.backoff-duration:300s") Duration backoff) {
this.dynDnsRouter = dynDnsRouter;
this.ipProvider = ipProvider;
this.publicIpLookup = publicIpLookup;
this.dnsResolver = dnsResolver;
this.config = config;
this.backoff = backoff;
@ -51,7 +51,7 @@ public class Updater {
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());
config.forEach(cfg -> {

View file

@ -0,0 +1,35 @@
package de.rpr.ddnsclient.lookup;
import jakarta.enterprise.context.ApplicationScoped;
import java.io.IOException;
@ApplicationScoped
class CurlProcessFactory {
enum IpClass {
V4,
V6
}
// Workaround: Can't get Mockito 5 to mock final classes
static class ProcessBuilderWrapper {
private final ProcessBuilder processBuilder;
ProcessBuilderWrapper(String... args) {
this.processBuilder = new ProcessBuilder(args);
}
Process start() throws IOException {
return processBuilder.start();
}
}
ProcessBuilderWrapper create(String provider, IpClass ipClass) {
if (ipClass == IpClass.V4) {
return new ProcessBuilderWrapper("curl", "--ipv4", provider);
} else {
return new ProcessBuilderWrapper("curl", "--ipv6", provider);
}
}
}

View file

@ -17,9 +17,20 @@ import java.util.NoSuchElementException;
import java.util.Optional;
@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 {
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();
void onStart(@Observes StartupEvent event) {
@ -63,11 +71,11 @@ public class IpProvider {
}
);
} catch (Exception e) {
Quarkus.asyncExit(1);
throw new IllegalStateException("Unknow provider configured!", e);
}
}
public IPs getPublicIps() {
public IPs get() {
log.trace("Retrieving public ips.");
try {
@ -76,9 +84,9 @@ public class IpProvider {
IPs.Builder ipsBuilder = new IPs.Builder();
try {
Process curl = new ProcessBuilder("curl", "--ipv4", provider).start();
curl.waitFor();
InputStream inputStream = curl.getInputStream();
Process curlProcess = curlProcessFactory.create(provider, CurlProcessFactory.IpClass.V4).start();
curlProcess.waitFor();
InputStream inputStream = curlProcess.getInputStream();
BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream));
String v4 = reader.readLine();
log.tracef("Retrieved ipv4: %s", v4);
@ -88,9 +96,9 @@ public class IpProvider {
}
try {
Process curl = new ProcessBuilder("curl", "--ipv6", provider).start();
curl.waitFor();
InputStream inputStream = curl.getInputStream();
Process curlProcess = curlProcessFactory.create(provider, CurlProcessFactory.IpClass.V6).start();
curlProcess.waitFor();
InputStream inputStream = curlProcess.getInputStream();
BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream));
String v6 = reader.readLine();
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.DynDnsRouter;
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.DyndnsAuth;
import de.rpr.ddnsclient.model.IPs;
@ -27,7 +27,7 @@ class UpdaterTest {
@Mock
DynDnsRouter dynDnsRouter;
@Mock
IpProvider ipProvider;
PublicIpLookup publicIpLookup;
@Mock
DnsResolver dnsResolver;
@ -40,7 +40,7 @@ class UpdaterTest {
@Test
void should_throw_exception_if_config_is_empty() {
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);
}
@ -48,7 +48,7 @@ class UpdaterTest {
void should_process_all_config_entries() {
IPs ips = new IPs("ipv4", "ipv6");
when(ipProvider.getPublicIps()).thenReturn(ips);
when(publicIpLookup.get()).thenReturn(ips);
when(dnsResolver.resolve(any())).thenReturn(ips);
Config multipleConfigEntries = new Config() {{
@ -56,7 +56,7 @@ class UpdaterTest {
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();
ArgumentCaptor<String> hostnameCaptor = ArgumentCaptor.forClass(String.class);
@ -71,10 +71,10 @@ class UpdaterTest {
IPs ips = new IPs("ipv4", "ipv6");
when(ipProvider.getPublicIps()).thenReturn(ips);
when(publicIpLookup.get()).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();
verify(dynDnsRouter, never()).get(any());
@ -89,10 +89,10 @@ class UpdaterTest {
when(dynDnsRouter.get("ddnss")).thenReturn(ddnss);
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"));
Updater updater = new Updater(dynDnsRouter, ipProvider, dnsResolver, ddnssConfig, backoff);
Updater updater = new Updater(dynDnsRouter, publicIpLookup, dnsResolver, ddnssConfig, backoff);
updater.run();
verify(ddnss).update(
@ -110,10 +110,10 @@ class UpdaterTest {
when(dynDnsRouter.get("ddnss")).thenReturn(ddnss);
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"));
Updater updater = new Updater(dynDnsRouter, ipProvider, dnsResolver, ddnssConfig, backoff);
Updater updater = new Updater(dynDnsRouter, publicIpLookup, dnsResolver, ddnssConfig, backoff);
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"));
CurlProcessFactory.ProcessBuilderWrapper v4CurlProcessBuilderWrapper = mock(CurlProcessFactory.ProcessBuilderWrapper.class, invocationOnMock -> v4CurlProcess);
when(curlProcessFactory.create(any(), eq(CurlProcessFactory.IpClass.V4))).thenReturn(v4CurlProcessBuilderWrapper);
Process v6CurlProcess = mock(Process.class);
when(v6CurlProcess.getInputStream()).thenReturn(IOUtils.toInputStream("ipv6", "UTF-8"));
CurlProcessFactory.ProcessBuilderWrapper v6CurlProcessBuilderWrapper = mock(CurlProcessFactory.ProcessBuilderWrapper.class, invocationOnMock -> v6CurlProcess);
when(curlProcessFactory.create(any(), eq(CurlProcessFactory.IpClass.V6))).thenReturn(v6CurlProcessBuilderWrapper);
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");
}
}