aboutsummaryrefslogtreecommitdiffstatshomepage
diff options
context:
space:
mode:
authorHarsh Shandilya <me@msfjarvis.dev>2020-03-14 10:32:12 +0530
committerHarsh Shandilya <me@msfjarvis.dev>2020-03-14 10:32:12 +0530
commit093139bc912018114f286edb269f1f8bc137c790 (patch)
tree57c78d16b97b625c9cfc35385062e7e5e5a37e02
parenttunnel: Remove MISSING_VALUE from BadConfigException reasons (diff)
downloadwireguard-android-093139bc912018114f286edb269f1f8bc137c790.tar.xz
wireguard-android-093139bc912018114f286edb269f1f8bc137c790.zip
tunnel: Add an initial set of unit tests
Includes a control set of broken configuration files that we attempt to parse and verify that the parser fails in a predictable and consistent manner. Signed-off-by: Harsh Shandilya <me@msfjarvis.dev>
-rw-r--r--.idea/inspectionProfiles/Default.xml27
-rw-r--r--build.gradle1
-rw-r--r--tunnel/build.gradle6
-rw-r--r--tunnel/src/test/java/com/wireguard/config/BadConfigExceptionTest.java172
-rw-r--r--tunnel/src/test/java/com/wireguard/config/ConfigTest.java45
-rw-r--r--tunnel/src/test/resources/broken.conf9
-rw-r--r--tunnel/src/test/resources/invalid-key.conf9
-rw-r--r--tunnel/src/test/resources/invalid-number.conf9
-rw-r--r--tunnel/src/test/resources/invalid-value.conf9
-rw-r--r--tunnel/src/test/resources/missing-attribute.conf8
-rw-r--r--tunnel/src/test/resources/missing-section.conf5
-rw-r--r--tunnel/src/test/resources/syntax-error.conf9
-rw-r--r--tunnel/src/test/resources/unknown-attribute.conf9
-rw-r--r--tunnel/src/test/resources/unknown-section.conf9
-rw-r--r--tunnel/src/test/resources/working.conf9
15 files changed, 323 insertions, 13 deletions
diff --git a/.idea/inspectionProfiles/Default.xml b/.idea/inspectionProfiles/Default.xml
index f048a11..dd76635 100644
--- a/.idea/inspectionProfiles/Default.xml
+++ b/.idea/inspectionProfiles/Default.xml
@@ -9,7 +9,6 @@
</option>
<option name="nonThreadSafeTypes" value="" />
</inspection_tool>
- <inspection_tool class="AndroidLintGoogleAppIndexingWarning" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="AndroidLintIconExpectedSize" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="AndroidLintNegativeMargin" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="AndroidLintTypographyQuotes" enabled="true" level="WARNING" enabled_by_default="true" />
@@ -40,7 +39,9 @@
<inspection_tool class="AssignmentToLambdaParameter" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="AssignmentToSuperclassField" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="AssignmentUsedAsCondition" enabled="true" level="WARNING" enabled_by_default="true" />
- <inspection_tool class="AutoCloseableResource" enabled="true" level="WARNING" enabled_by_default="true" />
+ <inspection_tool class="AutoCloseableResource" enabled="true" level="WARNING" enabled_by_default="true">
+ <option name="METHOD_MATCHER_CONFIG" value="java.util.Formatter,format,java.io.Writer,append,com.google.common.base.Preconditions,checkNotNull,org.hibernate.Session,close,java.io.PrintWriter,printf,java.util.HashMap,put,java.util.Map,put" />
+ </inspection_tool>
<inspection_tool class="BadExceptionCaught" enabled="true" level="WARNING" enabled_by_default="true">
<option name="exceptionsString" value="" />
<option name="exceptions">
@@ -59,6 +60,9 @@
<inspection_tool class="CallToStringConcatCanBeReplacedByOperator" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="CannotResolve" enabled="true" level="ERROR" enabled_by_default="true" />
<inspection_tool class="CastConflictsWithInstanceof" enabled="true" level="WARNING" enabled_by_default="true" />
+ <inspection_tool class="CatchMayIgnoreException" enabled="true" level="WARNING" enabled_by_default="true">
+ <option name="m_ignoreCatchBlocksWithComments" value="false" />
+ </inspection_tool>
<inspection_tool class="ChainedEquality" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="ClassInitializer" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="ClassNameDiffersFromFileName" enabled="true" level="WARNING" enabled_by_default="true" />
@@ -103,9 +107,6 @@
<inspection_tool class="DoubleLiteralMayBeFloatLiteral" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="DuplicateAlternationBranch" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="DuplicateBooleanBranch" enabled="true" level="WARNING" enabled_by_default="true" />
- <inspection_tool class="DuplicateCondition" enabled="true" level="WARNING" enabled_by_default="true">
- <option name="ignoreSideEffectConditions" value="true" />
- </inspection_tool>
<inspection_tool class="DuplicateDeclarations" enabled="true" level="ERROR" enabled_by_default="true" />
<inspection_tool class="DynamicRegexReplaceableByCompiledPattern" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="ElementOnlyUsedFromTestCode" enabled="false" level="WARNING" enabled_by_default="false" />
@@ -196,7 +197,6 @@
<inspection_tool class="ImplicitSubclassInspection" enabled="false" level="ERROR" enabled_by_default="false" />
<inspection_tool class="IncompatibleTypes" enabled="true" level="ERROR" enabled_by_default="true" />
<inspection_tool class="InitializerIssues" enabled="true" level="ERROR" enabled_by_default="true" />
- <inspection_tool class="InnerClassMayBeStatic" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="InnerClassReferencedViaSubclass" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="InnerClassVariableHidesOuterClassVariable" enabled="true" level="WARNING" enabled_by_default="true">
<option name="m_ignoreInvisibleFields" value="true" />
@@ -215,7 +215,6 @@
<inspection_tool class="InstanceofThis" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="InstantiationOfUtilityClass" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="IntLiteralMayBeLongLiteral" enabled="true" level="WARNING" enabled_by_default="true" />
- <inspection_tool class="IntegerDivisionInFloatingPointContext" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="IntegerTypeRequired" enabled="true" level="ERROR" enabled_by_default="true" />
<inspection_tool class="InterfaceMayBeAnnotatedFunctional" enabled="true" level="INFORMATION" enabled_by_default="true" />
<inspection_tool class="InterfaceNamingConvention" enabled="false" level="WARNING" enabled_by_default="false">
@@ -294,6 +293,7 @@
<option name="ignoreForLoopDeclarations" value="true" />
</inspection_tool>
<inspection_tool class="MultipleTopLevelClassesInFile" enabled="true" level="WARNING" enabled_by_default="true" />
+ <inspection_tool class="MultipleVariablesInDeclaration" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="NamedResource" enabled="true" level="ERROR" enabled_by_default="true" />
<inspection_tool class="NativeMethodNamingConvention" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="NegatedConditional" enabled="true" level="WARNING" enabled_by_default="true">
@@ -347,7 +347,6 @@
<inspection_tool class="OverriddenMethodCallDuringObjectConstruction" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="PackageInfoWithoutPackage" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="PointerTypeRequired" enabled="true" level="ERROR" enabled_by_default="true" />
- <inspection_tool class="PointlessNullCheck" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="ProblematicVarargsMethodOverride" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="ProtectedField" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="ProtectedInnerClass" enabled="true" level="WARNING" enabled_by_default="true">
@@ -394,7 +393,13 @@
<inspection_tool class="SizeReplaceableByIsEmpty" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="StaticCallOnSubclass" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="StaticFieldReferenceOnSubclass" enabled="true" level="WARNING" enabled_by_default="true" />
- <inspection_tool class="StaticImport" enabled="true" level="WARNING" enabled_by_default="true" />
+ <inspection_tool class="StaticImport" enabled="true" level="WARNING" enabled_by_default="true">
+ <option name="allowedClasses">
+ <set>
+ <option value="org.junit.Assert" />
+ </set>
+ </option>
+ </inspection_tool>
<inspection_tool class="StaticInheritance" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="StaticMethodNamingConvention" enabled="false" level="WARNING" enabled_by_default="false">
<option name="m_regex" value="[a-z][A-Za-z\d]*" />
@@ -432,7 +437,6 @@
<inspection_tool class="TemplateArgumentsIssues" enabled="true" level="ERROR" enabled_by_default="true" />
<inspection_tool class="TestMethodWithoutAssertion" enabled="false" level="WARNING" enabled_by_default="false">
<option name="assertionMethods" value="org.junit.Assert,assert.*|fail.*,junit.framework.Assert,assert.*|fail.*,org.junit.jupiter.api.Assertions,assert.*|fail.*,org.mockito.Mockito,verify.*,org.mockito.InOrder,verify,org.junit.rules.ExpectedException,expect.*,org.hamcrest.MatcherAssert,assertThat" />
- <option name="assertKeywordIsAssertion" value="false" />
</inspection_tool>
<inspection_tool class="TestNGMethodNamingConvention" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="ThisEscapedInConstructor" enabled="true" level="WARNING" enabled_by_default="true" />
@@ -440,7 +444,6 @@
<option name="ignoreRethrownExceptions" value="true" />
</inspection_tool>
<inspection_tool class="ThrowsRuntimeException" enabled="true" level="WARNING" enabled_by_default="true" />
- <inspection_tool class="ToArrayCallWithZeroLengthArrayArgument" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="TooBroadScope" enabled="true" level="WARNING" enabled_by_default="true">
<option name="m_allowConstructorAsInitializer" value="false" />
<option name="m_onlyLookAtBlocks" value="true" />
@@ -466,10 +469,8 @@
<inspection_tool class="UnnecessaryBlockStatement" enabled="true" level="WARNING" enabled_by_default="true">
<option name="ignoreSwitchBranches" value="false" />
</inspection_tool>
- <inspection_tool class="UnnecessaryCallToStringValueOf" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="UnnecessaryConstantArrayCreationExpression" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="UnnecessaryConstructor" enabled="true" level="WARNING" enabled_by_default="true" />
- <inspection_tool class="UnnecessaryDefault" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="UnnecessaryExplicitNumericCast" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="UnnecessaryFullyQualifiedName" enabled="true" level="WARNING" enabled_by_default="true">
<option name="m_ignoreJavadoc" value="false" />
diff --git a/build.gradle b/build.gradle
index 54d5d90..e735304 100644
--- a/build.gradle
+++ b/build.gradle
@@ -29,6 +29,7 @@ buildscript {
eddsaVersion = '0.3.0'
bintrayPluginVersion = '1.8.4'
mavenPluginVersion = '2.1'
+ junitVersion = '4.13'
groupName = 'com.wireguard.android'
}
diff --git a/tunnel/build.gradle b/tunnel/build.gradle
index 6a2e1c0..f306292 100644
--- a/tunnel/build.gradle
+++ b/tunnel/build.gradle
@@ -21,6 +21,11 @@ android {
path 'tools/CMakeLists.txt'
}
}
+ testOptions.unitTests.all {
+ testLogging {
+ events 'passed', 'skipped', 'failed', 'standardOut', 'standardError'
+ }
+ }
}
dependencies {
@@ -30,6 +35,7 @@ dependencies {
implementation "com.google.code.findbugs:jsr305:$jsr305Version"
implementation "com.jakewharton.threetenabp:threetenabp:$threetenabpVersion"
implementation "net.i2p.crypto:eddsa:$eddsaVersion"
+ testImplementation "junit:junit:$junitVersion"
}
apply from: "publish.gradle"
diff --git a/tunnel/src/test/java/com/wireguard/config/BadConfigExceptionTest.java b/tunnel/src/test/java/com/wireguard/config/BadConfigExceptionTest.java
new file mode 100644
index 0000000..5493551
--- /dev/null
+++ b/tunnel/src/test/java/com/wireguard/config/BadConfigExceptionTest.java
@@ -0,0 +1,172 @@
+/*
+ * Copyright © 2020 WireGuard LLC. All Rights Reserved.
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package com.wireguard.config;
+
+import com.wireguard.config.BadConfigException.Location;
+import com.wireguard.config.BadConfigException.Reason;
+import com.wireguard.config.BadConfigException.Section;
+
+import org.junit.AfterClass;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.HashMap;
+import java.util.Map;
+
+import static org.junit.Assert.*;
+
+public class BadConfigExceptionTest {
+ private static final String[] CONFIG_NAMES = {
+ "invalid-key",
+ "invalid-number",
+ "invalid-value",
+ "missing-attribute",
+ "missing-section",
+ "syntax-error",
+ "unknown-attribute",
+ "unknown-section"
+ };
+ private static final Map<String, InputStream> CONFIG_MAP = new HashMap<>();
+
+ @BeforeClass
+ public static void readConfigs() {
+ for (final String config: CONFIG_NAMES) {
+ CONFIG_MAP.put(config, BadConfigExceptionTest.class.getClassLoader().getResourceAsStream(config + ".conf"));
+ }
+ }
+
+ @AfterClass
+ public static void closeStreams() {
+ for (final InputStream inputStream : CONFIG_MAP.values()) {
+ try {
+ inputStream.close();
+ } catch (final IOException ignored) {
+ }
+ }
+ }
+
+ @Test
+ public void throws_correctly_with_INVALID_KEY_reason() {
+ try {
+ Config.parse(CONFIG_MAP.get("invalid-key"));
+ fail("Config parsing must fail in this test");
+ } catch (final BadConfigException e) {
+ assertEquals(e.getReason(), Reason.INVALID_KEY);
+ assertEquals(e.getLocation(), Location.PUBLIC_KEY);
+ assertEquals(e.getSection(), Section.PEER);
+ } catch (final IOException e) {
+ e.printStackTrace();
+ fail("IOException thrown during test");
+ }
+ }
+
+ @Test
+ public void throws_correctly_with_INVALID_NUMBER_reason() {
+ try {
+ Config.parse(CONFIG_MAP.get("invalid-number"));
+ fail("Config parsing must fail in this test");
+ } catch (final BadConfigException e) {
+ assertEquals(e.getReason(), Reason.INVALID_NUMBER);
+ assertEquals(e.getLocation(), Location.PERSISTENT_KEEPALIVE);
+ assertEquals(e.getSection(), Section.PEER);
+ } catch (final IOException e) {
+ e.printStackTrace();
+ fail("IOException thrown during test");
+ }
+ }
+
+ @Test
+ public void throws_correctly_with_INVALID_VALUE_reason() {
+ try {
+ Config.parse(CONFIG_MAP.get("invalid-value"));
+ fail("Config parsing must fail in this test");
+ } catch (final BadConfigException e) {
+ assertEquals(e.getReason(), Reason.INVALID_VALUE);
+ assertEquals(e.getLocation(), Location.DNS);
+ assertEquals(e.getSection(), Section.INTERFACE);
+ } catch (final IOException e) {
+ e.printStackTrace();
+ fail("IOException throwing during test");
+ }
+ }
+
+ @Test
+ public void throws_correctly_with_MISSING_ATTRIBUTE_reason() {
+ try {
+ Config.parse(CONFIG_MAP.get("missing-attribute"));
+ fail("Config parsing must fail in this test");
+ } catch (final BadConfigException e) {
+ assertEquals(e.getReason(), Reason.MISSING_ATTRIBUTE);
+ assertEquals(e.getLocation(), Location.PUBLIC_KEY);
+ assertEquals(e.getSection(), Section.PEER);
+ } catch (final IOException e) {
+ e.printStackTrace();
+ fail("IOException throwing during test");
+ }
+ }
+
+ @Test
+ public void throws_correctly_with_MISSING_SECTION_reason() {
+ try {
+ Config.parse(CONFIG_MAP.get("missing-section"));
+ fail("Config parsing must fail in this test");
+ } catch (final BadConfigException e) {
+ assertEquals(e.getReason(), Reason.MISSING_SECTION);
+ assertEquals(e.getLocation(), Location.TOP_LEVEL);
+ assertEquals(e.getSection(), Section.CONFIG);
+ } catch (final IOException e) {
+ e.printStackTrace();
+ fail("IOException throwing during test");
+ }
+ }
+
+ @Test
+ public void throws_correctly_with_SYNTAX_ERROR_reason() {
+ try {
+ Config.parse(CONFIG_MAP.get("syntax-error"));
+ fail("Config parsing must fail in this test");
+ } catch (final BadConfigException e) {
+ assertEquals(e.getReason(), Reason.SYNTAX_ERROR);
+ assertEquals(e.getLocation(), Location.TOP_LEVEL);
+ assertEquals(e.getSection(), Section.PEER);
+ } catch (final IOException e) {
+ e.printStackTrace();
+ fail("IOException throwing during test");
+ }
+ }
+
+ @Test
+ public void throws_correctly_with_UNKNOWN_ATTRIBUTE_reason() {
+ try {
+ Config.parse(CONFIG_MAP.get("unknown-attribute"));
+ fail("Config parsing must fail in this test");
+ } catch (final BadConfigException e) {
+ assertEquals(e.getReason(), Reason.UNKNOWN_ATTRIBUTE);
+ assertEquals(e.getLocation(), Location.TOP_LEVEL);
+ assertEquals(e.getSection(), Section.PEER);
+ } catch (final IOException e) {
+ e.printStackTrace();
+ fail("IOException throwing during test");
+ }
+ }
+
+ @Test
+ public void throws_correctly_with_UNKNOWN_SECTION_reason() {
+ try {
+ Config.parse(CONFIG_MAP.get("unknown-section"));
+ fail("Config parsing must fail in this test");
+ } catch (final BadConfigException e) {
+ assertEquals(e.getReason(), Reason.UNKNOWN_SECTION);
+ assertEquals(e.getLocation(), Location.TOP_LEVEL);
+ assertEquals(e.getSection(), Section.CONFIG);
+ } catch (final IOException e) {
+ e.printStackTrace();
+ fail("IOException throwing during test");
+ }
+ }
+}
diff --git a/tunnel/src/test/java/com/wireguard/config/ConfigTest.java b/tunnel/src/test/java/com/wireguard/config/ConfigTest.java
new file mode 100644
index 0000000..6d59921
--- /dev/null
+++ b/tunnel/src/test/java/com/wireguard/config/ConfigTest.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright © 2020 WireGuard LLC. All Rights Reserved.
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package com.wireguard.config;
+
+import static org.junit.Assert.*;
+import org.junit.Test;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.Objects;
+
+public class ConfigTest {
+
+ @Test
+ public void valid_config_parses_correctly() throws IOException, ParseException {
+ Config config = null;
+ final Collection<InetNetwork> expectedAllowedIps = new HashSet<>(Arrays.asList(InetNetwork.parse("0.0.0.0/0"), InetNetwork.parse("::0/0")));
+ try (final InputStream is = Objects.requireNonNull(getClass().getClassLoader()).getResourceAsStream("working.conf")) {
+ config = Config.parse(is);
+ } catch (final BadConfigException e) {
+ fail("'working.conf' should never fail to parse");
+ }
+ assertNotNull("config cannot be null after parsing", config);
+ assertTrue(
+ "No applications should be excluded by default",
+ config.getInterface().getExcludedApplications().isEmpty()
+ );
+ assertEquals("Test config has exactly one peer", 1, config.getPeers().size());
+ assertEquals("Test config's allowed IPs are 0.0.0.0/0 and ::0/0", config.getPeers().get(0).getAllowedIps(), expectedAllowedIps);
+ assertEquals("Test config has one DNS server", 1, config.getInterface().getDnsServers().size());
+ }
+
+ @Test(expected = BadConfigException.class)
+ public void invalid_config_throws() throws IOException, BadConfigException {
+ try (final InputStream is = Objects.requireNonNull(getClass().getClassLoader()).getResourceAsStream("broken.conf")) {
+ Config.parse(is);
+ }
+ }
+}
diff --git a/tunnel/src/test/resources/broken.conf b/tunnel/src/test/resources/broken.conf
new file mode 100644
index 0000000..753c971
--- /dev/null
+++ b/tunnel/src/test/resources/broken.conf
@@ -0,0 +1,9 @@
+[Interface]
+PrivateKey = l0lth1s1sd3f1n1t3lybr0k3n=
+Address = 192.0.2.2/32,2001:db8:ffff:ffff:ffff:ffff:ffff:ffff/128
+DNS = 192.0.2.0
+
+[Peer]
+PublicKey = vBN7qyUTb5lJtWYJ8LhbPio1Z4RcyBPGnqFBGn6O6Qg=
+AllowedIPs = 0.0.0.0/0,::0/0
+Endpoint = 192.0.2.1:51820
diff --git a/tunnel/src/test/resources/invalid-key.conf b/tunnel/src/test/resources/invalid-key.conf
new file mode 100644
index 0000000..215bec3
--- /dev/null
+++ b/tunnel/src/test/resources/invalid-key.conf
@@ -0,0 +1,9 @@
+[Interface]
+Address = 192.0.2.2/32,2001:db8:ffff:ffff:ffff:ffff:ffff:ffff/128
+DNS = 192.0.2.0
+PrivateKey = TFlmmEUC7V7VtiDYLKsbP5rySTKLIZq1yn8lMqK83wo=
+[Peer]
+AllowedIPs = 0.0.0.0/0, ::0/0
+Endpoint = 192.0.2.1:51820
+PersistentKeepalive = 0
+PublicKey = vBN7qyUTb5lJtWYJ8LhbPio1Z4RcyBPGnqFBGn6Og=
diff --git a/tunnel/src/test/resources/invalid-number.conf b/tunnel/src/test/resources/invalid-number.conf
new file mode 100644
index 0000000..f05fe32
--- /dev/null
+++ b/tunnel/src/test/resources/invalid-number.conf
@@ -0,0 +1,9 @@
+[Interface]
+Address = 192.0.2.2/32,2001:db8:ffff:ffff:ffff:ffff:ffff:ffff/128
+DNS = 192.0.2.0
+PrivateKey = TFlmmEUC7V7VtiDYLKsbP5rySTKLIZq1yn8lMqK83wo=
+[Peer]
+AllowedIPs = 0.0.0.0/0, ::0/0
+Endpoint = 192.0.2.1:51820
+PersistentKeepalive = 0L
+PublicKey = vBN7qyUTb5lJtWYJ8LhbPio1Z4RcyBPGnqFBGn6O6Qg=
diff --git a/tunnel/src/test/resources/invalid-value.conf b/tunnel/src/test/resources/invalid-value.conf
new file mode 100644
index 0000000..2889111
--- /dev/null
+++ b/tunnel/src/test/resources/invalid-value.conf
@@ -0,0 +1,9 @@
+[Interface]
+Address = 192.0.2.2/32,2001:db8:ffff:ffff:ffff:ffff:ffff:ffff/128
+DNS = 192.0.2.0,yes
+PrivateKey = TFlmmEUC7V7VtiDYLKsbP5rySTKLIZq1yn8lMqK83wo=
+[Peer]
+AllowedIPs = 0.0.0.0/0, ::0/0
+Endpoint = 192.0.2.1:51820
+PersistentKeepalive = 0
+PublicKey = vBN7qyUTb5lJtWYJ8LhbPio1Z4RcyBPGnqFBGn6O6Qg=
diff --git a/tunnel/src/test/resources/missing-attribute.conf b/tunnel/src/test/resources/missing-attribute.conf
new file mode 100644
index 0000000..ddf8cbb
--- /dev/null
+++ b/tunnel/src/test/resources/missing-attribute.conf
@@ -0,0 +1,8 @@
+[Interface]
+Address = 192.0.2.2/32,2001:db8:ffff:ffff:ffff:ffff:ffff:ffff/128
+DNS = 192.0.2.0
+PrivateKey = TFlmmEUC7V7VtiDYLKsbP5rySTKLIZq1yn8lMqK83wo=
+[Peer]
+AllowedIPs = 0.0.0.0/0, ::0/0
+Endpoint = 192.0.2.1:51820
+PersistentKeepalive = 0
diff --git a/tunnel/src/test/resources/missing-section.conf b/tunnel/src/test/resources/missing-section.conf
new file mode 100644
index 0000000..676199a
--- /dev/null
+++ b/tunnel/src/test/resources/missing-section.conf
@@ -0,0 +1,5 @@
+[Peer]
+AllowedIPs = 0.0.0.0/0, ::0/0
+Endpoint = 192.0.2.1:51820
+PersistentKeepalive = 0
+PublicKey = vBN7qyUTb5lJtWYJ8LhbPio1Z4RcyBPGnqFBGn6O6Qg=
diff --git a/tunnel/src/test/resources/syntax-error.conf b/tunnel/src/test/resources/syntax-error.conf
new file mode 100644
index 0000000..38b8ec9
--- /dev/null
+++ b/tunnel/src/test/resources/syntax-error.conf
@@ -0,0 +1,9 @@
+[Interface]
+Address = 192.0.2.2/32,2001:db8:ffff:ffff:ffff:ffff:ffff:ffff/128
+DNS = 192.0.2.0
+PrivateKey = TFlmmEUC7V7VtiDYLKsbP5rySTKLIZq1yn8lMqK83wo=
+[Peer]
+AllowedIPs = 0.0.0.0/0, ::0/0
+Endpoint =
+PersistentKeepalive = 0
+PublicKey = vBN7qyUTb5lJtWYJ8LhbPio1Z4RcyBPGnqFBGn6O6Qg=
diff --git a/tunnel/src/test/resources/unknown-attribute.conf b/tunnel/src/test/resources/unknown-attribute.conf
new file mode 100644
index 0000000..f311161
--- /dev/null
+++ b/tunnel/src/test/resources/unknown-attribute.conf
@@ -0,0 +1,9 @@
+[Interface]
+Address = 192.0.2.2/32,2001:db8:ffff:ffff:ffff:ffff:ffff:ffff/128
+DNS = 192.0.2.0
+PrivateKey = TFlmmEUC7V7VtiDYLKsbP5rySTKLIZq1yn8lMqK83wo=
+[Peer]
+AllowedIPs = 0.0.0.0/0, ::0/0
+Endpoint = 192.0.2.1:51820
+DontLetTheFeelingFade = 1
+PublicKey = vBN7qyUTb5lJtWYJ8LhbPio1Z4RcyBPGnqFBGn6O6Qg=
diff --git a/tunnel/src/test/resources/unknown-section.conf b/tunnel/src/test/resources/unknown-section.conf
new file mode 100644
index 0000000..579d971
--- /dev/null
+++ b/tunnel/src/test/resources/unknown-section.conf
@@ -0,0 +1,9 @@
+[Interface]
+Address = 192.0.2.2/32,2001:db8:ffff:ffff:ffff:ffff:ffff:ffff/128
+DNS = 192.0.2.0
+PrivateKey = TFlmmEUC7V7VtiDYLKsbP5rySTKLIZq1yn8lMqK83wo=
+[Peers]
+AllowedIPs = 0.0.0.0/0, ::0/0
+Endpoint = 192.0.2.1:51820
+PersistentKeepalive = 0
+PublicKey = vBN7qyUTb5lJtWYJ8LhbPio1Z4RcyBPGnqFBGn6O6Qg=
diff --git a/tunnel/src/test/resources/working.conf b/tunnel/src/test/resources/working.conf
new file mode 100644
index 0000000..3f9665c
--- /dev/null
+++ b/tunnel/src/test/resources/working.conf
@@ -0,0 +1,9 @@
+[Interface]
+Address = 192.0.2.2/32,2001:db8:ffff:ffff:ffff:ffff:ffff:ffff/128
+DNS = 192.0.2.0
+PrivateKey = TFlmmEUC7V7VtiDYLKsbP5rySTKLIZq1yn8lMqK83wo=
+[Peer]
+AllowedIPs = 0.0.0.0/0, ::0/0
+Endpoint = 192.0.2.1:51820
+PersistentKeepalive = 0
+PublicKey = vBN7qyUTb5lJtWYJ8LhbPio1Z4RcyBPGnqFBGn6O6Qg=