Integration tests for mailbox using mailbox-lib as submodule

This commit is contained in:
Sebastian Kürten
2022-09-21 16:08:19 +02:00
parent 34815eb1a5
commit 459b97c1d4
41 changed files with 858 additions and 146 deletions

4
mailbox-integration-tests/.gitignore vendored Normal file
View File

@@ -0,0 +1,4 @@
bin
build
test.tmp
.settings

View File

@@ -0,0 +1,51 @@
apply plugin: 'java-library'
sourceCompatibility = 1.8
targetCompatibility = 1.8
apply plugin: 'ru.vyarus.animalsniffer'
apply plugin: 'idea'
apply from: '../dagger.gradle'
dependencies {
implementation project(path: ':bramble-api', configuration: 'default')
implementation project(path: ':bramble-core', configuration: 'default')
annotationProcessor "com.google.dagger:dagger-compiler:$dagger_version"
testImplementation project(path: ':bramble-api', configuration: 'testOutput')
testImplementation project(path: ':bramble-core', configuration: 'testOutput')
testImplementation 'org.hsqldb:hsqldb:2.3.5' // The last version that supports Java 1.6
testImplementation 'net.jodah:concurrentunit:0.4.2'
testImplementation "junit:junit:$junit_version"
testImplementation "org.jmock:jmock:$jmock_version"
testImplementation "org.jmock:jmock-junit4:$jmock_version"
testImplementation "org.jmock:jmock-imposters:$jmock_version"
testImplementation "com.squareup.okhttp3:mockwebserver:4.9.3"
testImplementation project(path: ':mailbox-core', configuration: 'default')
testImplementation project(path: ':mailbox-lib', configuration: 'default')
testImplementation "ch.qos.logback:logback-classic:1.2.11"
testAnnotationProcessor "com.google.dagger:dagger-compiler:$dagger_version"
signature 'org.codehaus.mojo.signature:java16:1.1@signature'
}
animalsniffer {
// Allow requireNonNull: Android desugaring rewrites it (so it's safe for us to use),
// and it gets used when passing method references instead of lambdas with Java 11.
// Note that this line allows *all* methods from java.util.Objects.
// That's the best that we can do with the configuration options that Animal Sniffer offers.
ignore 'java.util.Objects'
}
// needed to make test output available to bramble-java
configurations {
testOutput.extendsFrom(testCompile)
}
task jarTest(type: Jar, dependsOn: testClasses) {
from sourceSets.test.output
classifier = 'test'
}
artifacts {
testOutput jarTest
}

View File

@@ -0,0 +1,267 @@
package org.briarproject.bramble.mailbox;
import org.briarproject.bramble.api.contact.ContactId;
import org.briarproject.bramble.api.mailbox.InvalidMailboxIdException;
import org.briarproject.bramble.api.mailbox.MailboxAuthToken;
import org.briarproject.bramble.api.mailbox.MailboxFileId;
import org.briarproject.bramble.api.mailbox.MailboxFolderId;
import org.briarproject.bramble.api.mailbox.MailboxProperties;
import org.briarproject.bramble.mailbox.MailboxApi.ApiException;
import org.briarproject.bramble.mailbox.MailboxApi.MailboxContact;
import org.briarproject.bramble.mailbox.MailboxApi.MailboxFile;
import org.briarproject.bramble.mailbox.MailboxApi.TolerableFailureException;
import org.briarproject.bramble.test.BrambleTestCase;
import org.briarproject.mailbox.lib.Mailbox;
import org.junit.After;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TemporaryFolder;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import static java.util.Collections.emptyList;
import static java.util.Collections.singletonList;
import static org.briarproject.bramble.mailbox.MailboxIntegrationTestUtils.URL_BASE;
import static org.briarproject.bramble.mailbox.MailboxIntegrationTestUtils.createMailboxApi;
import static org.briarproject.bramble.test.TestUtils.getRandomBytes;
import static org.briarproject.bramble.test.TestUtils.getRandomId;
import static org.briarproject.bramble.test.TestUtils.readBytes;
import static org.briarproject.bramble.test.TestUtils.writeBytes;
import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertThrows;
import static org.junit.Assert.assertTrue;
public class MailboxApiIntegrationTest extends BrambleTestCase {
@Rule
public TemporaryFolder folder = new TemporaryFolder();
@Rule
public TemporaryFolder dataDirectory = new TemporaryFolder();
private Mailbox mailbox;
private MailboxAuthToken setupToken;
private final MailboxApi api = createMailboxApi();
private MailboxProperties ownerProperties;
@Before
public void setUp()
throws IOException, ApiException, InvalidMailboxIdException {
mailbox = new Mailbox(dataDirectory.getRoot());
mailbox.init();
mailbox.startLifecycle();
setupToken = MailboxAuthToken.fromString(mailbox.getSetupToken());
assertNull(ownerProperties);
MailboxProperties setupProperties = new MailboxProperties(
URL_BASE, setupToken, new ArrayList<>());
ownerProperties = api.setup(setupProperties);
}
@After
public void tearDown() {
mailbox.stopLifecycle();
}
@Test
public void wipe() throws IOException, ApiException {
api.wipeMailbox(ownerProperties);
// check doesn't work anymore
assertThrows(ApiException.class,
() -> api.checkStatus(ownerProperties));
// new setup doesn't work as mailbox is stopping
MailboxProperties setupProperties = new MailboxProperties(
URL_BASE, setupToken, new ArrayList<>());
assertThrows(ApiException.class, () -> api.setup(setupProperties));
}
@Test
public void testStatus() throws Exception {
// Owner calls status endpoint
assertTrue(api.checkStatus(ownerProperties));
// Owner adds contact
ContactId contactId = new ContactId(1);
MailboxContact contact = getMailboxContact(contactId);
MailboxProperties contactProperties = new MailboxProperties(
ownerProperties.getOnion(), contact.token,
new ArrayList<>(), contact.inboxId, contact.outboxId);
api.addContact(ownerProperties, contact);
// Contact calls status endpoint
assertTrue(api.checkStatus(contactProperties));
// Owner deletes contact again to leave clean state for other tests
api.deleteContact(ownerProperties, contactId);
assertEquals(emptyList(), api.getContacts(ownerProperties));
}
@Test
public void testContactApi() throws Exception {
ContactId contactId1 = new ContactId(1);
ContactId contactId2 = new ContactId(2);
MailboxContact mailboxContact1 = getMailboxContact(contactId1);
MailboxContact mailboxContact2 = getMailboxContact(contactId2);
// no contacts initially
assertEquals(emptyList(), api.getContacts(ownerProperties));
// added contact gets returned
api.addContact(ownerProperties, mailboxContact1);
assertEquals(singletonList(contactId1),
api.getContacts(ownerProperties));
// second contact also gets returned
api.addContact(ownerProperties, mailboxContact2);
assertEquals(Arrays.asList(contactId1, contactId2),
api.getContacts(ownerProperties));
// after both contacts get deleted, the list is empty again
api.deleteContact(ownerProperties, contactId1);
api.deleteContact(ownerProperties, contactId2);
assertEquals(emptyList(), api.getContacts(ownerProperties));
// deleting again is tolerable
assertThrows(TolerableFailureException.class,
() -> api.deleteContact(ownerProperties, contactId2));
}
@Test
public void testFileManagementApi() throws Exception {
// add contact, so we can leave each other files
ContactId contactId = new ContactId(1);
MailboxContact contact = getMailboxContact(contactId);
MailboxProperties contactProperties = new MailboxProperties(
ownerProperties.getOnion(), contact.token,
new ArrayList<>(), contact.inboxId, contact.outboxId);
api.addContact(ownerProperties, contact);
// upload a file for our contact
File file1 = folder.newFile();
byte[] bytes1 = getRandomBytes(2048);
writeBytes(file1, bytes1);
api.addFile(ownerProperties, contact.inboxId, file1);
// contact checks files
List<MailboxFile> files1 =
api.getFiles(contactProperties, contact.inboxId);
assertEquals(1, files1.size());
MailboxFileId fileName1 = files1.get(0).name;
// owner can't check files
assertThrows(TolerableFailureException.class, () ->
api.getFiles(ownerProperties, contact.inboxId));
// contact downloads file
File file1downloaded = folder.newFile();
api.getFile(contactProperties, contact.inboxId, fileName1,
file1downloaded);
assertArrayEquals(bytes1, readBytes(file1downloaded));
// owner can't download file, even if knowing name
File file1forbidden = folder.newFile();
assertThrows(TolerableFailureException.class, () ->
api.getFile(ownerProperties, contact.inboxId, fileName1,
file1forbidden));
assertEquals(0, file1forbidden.length());
// owner can't delete file
assertThrows(TolerableFailureException.class, () ->
api.deleteFile(ownerProperties, contact.inboxId, fileName1));
// contact deletes file
api.deleteFile(contactProperties, contact.inboxId, fileName1);
assertEquals(0,
api.getFiles(contactProperties, contact.inboxId).size());
// contact uploads two files for the owner
File file2 = folder.newFile();
File file3 = folder.newFile();
byte[] bytes2 = getRandomBytes(2048);
byte[] bytes3 = getRandomBytes(1024);
writeBytes(file2, bytes2);
writeBytes(file3, bytes3);
api.addFile(contactProperties, contact.outboxId, file2);
api.addFile(contactProperties, contact.outboxId, file3);
// owner checks folders with available files
List<MailboxFolderId> folders = api.getFolders(ownerProperties);
assertEquals(singletonList(contact.outboxId), folders);
// owner lists files in contact's outbox
List<MailboxFile> files2 =
api.getFiles(ownerProperties, contact.outboxId);
assertEquals(2, files2.size());
MailboxFileId file2name = files2.get(0).name;
MailboxFileId file3name = files2.get(1).name;
// contact can't list files in contact's outbox
assertThrows(TolerableFailureException.class, () ->
api.getFiles(contactProperties, contact.outboxId));
// owner downloads both files from contact's outbox
File file2downloaded = folder.newFile();
File file3downloaded = folder.newFile();
api.getFile(ownerProperties, contact.outboxId, file2name,
file2downloaded);
api.getFile(ownerProperties, contact.outboxId, file3name,
file3downloaded);
byte[] downloadedBytes2 = readBytes(file2downloaded);
byte[] downloadedBytes3 = readBytes(file3downloaded);
// file order is preserved (sorted by time),
// so we know what file is which
assertArrayEquals(bytes2, downloadedBytes2);
assertArrayEquals(bytes3, downloadedBytes3);
// contact can't download files again, even if knowing name
File file2forbidden = folder.newFile();
File file3forbidden = folder.newFile();
assertThrows(TolerableFailureException.class, () ->
api.getFile(contactProperties, contact.outboxId, file2name,
file2forbidden));
assertThrows(TolerableFailureException.class, () ->
api.getFile(contactProperties, contact.outboxId, file3name,
file3forbidden));
assertEquals(0, file1forbidden.length());
assertEquals(0, file2forbidden.length());
// contact can't delete files in outbox
assertThrows(TolerableFailureException.class, () ->
api.deleteFile(contactProperties, contact.outboxId, file2name));
assertThrows(TolerableFailureException.class, () ->
api.deleteFile(contactProperties, contact.outboxId, file3name));
// owner deletes files
api.deleteFile(ownerProperties, contact.outboxId, file2name);
api.deleteFile(ownerProperties, contact.outboxId, file3name);
assertEquals(emptyList(),
api.getFiles(ownerProperties, contact.outboxId));
assertEquals(emptyList(), api.getFolders(ownerProperties));
// deleting a non-existent file is tolerable
assertThrows(TolerableFailureException.class, () ->
api.deleteFile(ownerProperties, contact.outboxId, file3name));
// owner deletes contact again to leave clean state for other tests
api.deleteContact(ownerProperties, contactId);
assertEquals(emptyList(), api.getContacts(ownerProperties));
}
private MailboxContact getMailboxContact(ContactId contactId) {
MailboxAuthToken authToken = new MailboxAuthToken(getRandomId());
MailboxFolderId inboxId = new MailboxFolderId(getRandomId());
MailboxFolderId outboxId = new MailboxFolderId(getRandomId());
return new MailboxContact(contactId, authToken, inboxId, outboxId);
}
}

View File

@@ -0,0 +1,56 @@
package org.briarproject.bramble.mailbox;
import org.briarproject.bramble.BrambleCoreModule;
import org.briarproject.bramble.api.contact.ContactManager;
import org.briarproject.bramble.api.crypto.CryptoComponent;
import org.briarproject.bramble.api.db.DatabaseComponent;
import org.briarproject.bramble.api.identity.AuthorFactory;
import org.briarproject.bramble.api.lifecycle.LifecycleManager;
import org.briarproject.bramble.api.mailbox.MailboxManager;
import org.briarproject.bramble.api.mailbox.MailboxSettingsManager;
import org.briarproject.bramble.api.mailbox.MailboxUpdateManager;
import org.briarproject.bramble.api.properties.TransportPropertyManager;
import org.briarproject.bramble.api.system.Clock;
import org.briarproject.bramble.mailbox.MailboxIntegrationTestUtils.TestUrlConverterModule;
import org.briarproject.bramble.test.BrambleCoreIntegrationTestModule;
import org.briarproject.bramble.test.BrambleIntegrationTestComponent;
import org.briarproject.bramble.test.FakeTorPluginConfigModule;
import org.briarproject.bramble.test.TestDnsModule;
import org.briarproject.bramble.test.TestSocksModule;
import javax.inject.Singleton;
import dagger.Component;
@Singleton
@Component(modules = {
BrambleCoreIntegrationTestModule.class,
BrambleCoreModule.class,
TestUrlConverterModule.class,
FakeTorPluginConfigModule.class,
TestSocksModule.class,
TestDnsModule.class,
})
interface MailboxIntegrationTestComponent extends
BrambleIntegrationTestComponent {
DatabaseComponent getDatabaseComponent();
MailboxManager getMailboxManager();
MailboxUpdateManager getMailboxUpdateManager();
MailboxSettingsManager getMailboxSettingsManager();
LifecycleManager getLifecycleManager();
ContactManager getContactManager();
Clock getClock();
TransportPropertyManager getTransportPropertyManager();
AuthorFactory getAuthorFactory();
CryptoComponent getCrypto();
}

View File

@@ -0,0 +1,148 @@
package org.briarproject.bramble.mailbox;
import org.briarproject.bramble.BrambleCoreIntegrationTestEagerSingletons;
import org.briarproject.bramble.api.WeakSingletonProvider;
import org.briarproject.bramble.api.mailbox.MailboxAuthToken;
import org.briarproject.bramble.io.IoModule;
import org.briarproject.bramble.test.TestDatabaseConfigModule;
import java.io.File;
import java.nio.ByteBuffer;
import java.nio.charset.Charset;
import java.util.Arrays;
import java.util.concurrent.atomic.AtomicBoolean;
import javax.annotation.Nonnull;
import javax.inject.Singleton;
import javax.net.SocketFactory;
import dagger.Module;
import dagger.Provides;
import okhttp3.OkHttpClient;
import static java.lang.System.currentTimeMillis;
import static java.util.concurrent.TimeUnit.MILLISECONDS;
import static org.junit.Assert.fail;
class MailboxIntegrationTestUtils {
static final String URL_BASE = "http://127.0.0.1:8000";
static String getQrCodePayload(MailboxAuthToken setupToken) {
byte[] bytes = getQrCodeBytes(setupToken);
Charset charset = Charset.forName("ISO-8859-1");
return new String(bytes, charset);
}
private static byte[] getQrCodeBytes(MailboxAuthToken setupToken) {
byte[] hiddenServiceBytes = getHiddenServiceBytes();
byte[] setupTokenBytes = setupToken.getBytes();
return ByteBuffer.allocate(65).put((byte) 32)
.put(hiddenServiceBytes).put(setupTokenBytes).array();
}
private static byte[] getHiddenServiceBytes() {
byte[] data = new byte[32];
Arrays.fill(data, (byte) 'a');
return data;
}
private static WeakSingletonProvider<OkHttpClient> createHttpClientProvider() {
OkHttpClient client = new OkHttpClient.Builder()
.socketFactory(SocketFactory.getDefault())
.connectTimeout(60_000, MILLISECONDS)
.build();
return new WeakSingletonProvider<OkHttpClient>() {
@Override
@Nonnull
public OkHttpClient createInstance() {
return client;
}
};
}
static MailboxApi createMailboxApi() {
return new MailboxApiImpl(createHttpClientProvider(),
new TestUrlConverter());
}
static MailboxIntegrationTestComponent createTestComponent(
File databaseDir) {
MailboxIntegrationTestComponent component =
DaggerMailboxIntegrationTestComponent
.builder()
.testDatabaseConfigModule(
new TestDatabaseConfigModule(databaseDir))
.ioModule(new TestIoModule())
.build();
BrambleCoreIntegrationTestEagerSingletons.Helper
.injectEagerSingletons(component);
return component;
}
@Module
static class TestIoModule extends IoModule {
@Provides
@Singleton
WeakSingletonProvider<OkHttpClient> provideOkHttpClientProvider() {
return createHttpClientProvider();
}
}
@Module
static class TestUrlConverterModule {
@Provides
UrlConverter provideUrlConverter() {
return new TestUrlConverter();
}
}
interface Check {
boolean check();
}
/**
* Run the specified method {@code check} every {@code step} milliseconds
* until either {@code check} returns true or longer than {@code totalTime}
* milliseconds have been spent checking the function and waiting for the
* next invocation.
*/
static void retryUntilSuccessOrTimeout(long totalTime, long step,
Check check) {
AtomicBoolean success = new AtomicBoolean(false);
checkRepeatedly(totalTime, step, () -> {
boolean result = check.check();
if (result) success.set(true);
return result;
}
);
if (!success.get()) {
fail("timeout reached");
}
}
/**
* Run the specified method {@code check} every {@code step} milliseconds
* until either {@code check} returns true or longer than {@code totalTime}
* milliseconds have been spent checking the function and waiting for the
* next invocation.
*/
private static void checkRepeatedly(long totalTime, long step,
Check check) {
long start = currentTimeMillis();
while (currentTimeMillis() - start < totalTime) {
if (check.check()) {
return;
}
try {
Thread.sleep(step);
} catch (InterruptedException ignore) {
// continue
}
}
}
}

View File

@@ -0,0 +1,156 @@
package org.briarproject.bramble.mailbox;
import org.briarproject.bramble.api.contact.ContactId;
import org.briarproject.bramble.api.contact.ContactManager;
import org.briarproject.bramble.api.crypto.SecretKey;
import org.briarproject.bramble.api.db.DbException;
import org.briarproject.bramble.api.identity.Author;
import org.briarproject.bramble.api.identity.AuthorFactory;
import org.briarproject.bramble.api.identity.Identity;
import org.briarproject.bramble.api.identity.IdentityManager;
import org.briarproject.bramble.api.lifecycle.LifecycleManager;
import org.briarproject.bramble.api.mailbox.MailboxAuthToken;
import org.briarproject.bramble.api.mailbox.MailboxPairingState.Paired;
import org.briarproject.bramble.api.mailbox.MailboxPairingTask;
import org.briarproject.bramble.api.mailbox.MailboxProperties;
import org.briarproject.bramble.mailbox.MailboxApi.ApiException;
import org.briarproject.bramble.test.BrambleTestCase;
import org.briarproject.mailbox.lib.Mailbox;
import org.junit.After;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TemporaryFolder;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.logging.Logger;
import static java.util.logging.Level.WARNING;
import static java.util.logging.Logger.getLogger;
import static org.briarproject.bramble.api.mailbox.MailboxAuthToken.fromString;
import static org.briarproject.bramble.mailbox.MailboxIntegrationTestUtils.createMailboxApi;
import static org.briarproject.bramble.mailbox.MailboxIntegrationTestUtils.createTestComponent;
import static org.briarproject.bramble.mailbox.MailboxIntegrationTestUtils.getQrCodePayload;
import static org.briarproject.bramble.mailbox.MailboxIntegrationTestUtils.retryUntilSuccessOrTimeout;
import static org.briarproject.bramble.test.TestUtils.getSecretKey;
import static org.briarproject.bramble.test.TestUtils.getTestDirectory;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.fail;
public class OwnMailboxContactListWorkerIntegrationTest
extends BrambleTestCase {
private static final Logger LOG = getLogger(
OwnMailboxContactListWorkerIntegrationTest.class.getName());
@Rule
public TemporaryFolder mailboxDataDirectory = new TemporaryFolder();
private Mailbox mailbox;
private final MailboxApi api = createMailboxApi();
private MailboxProperties ownerProperties;
private final File testDir = getTestDirectory();
private final File aliceDir = new File(testDir, "alice");
private MailboxIntegrationTestComponent component;
private Identity identity;
private final SecretKey rootKey = getSecretKey();
private final long timestamp = System.currentTimeMillis();
@Before
public void setUp() throws Exception {
mailbox = new Mailbox(mailboxDataDirectory.getRoot());
mailbox.init();
mailbox.startLifecycle();
MailboxAuthToken setupToken = fromString(mailbox.getSetupToken());
component = createTestComponent(aliceDir);
identity = setUp(component, "Alice");
MailboxPairingTask pairingTask = component.getMailboxManager()
.startPairingTask(getQrCodePayload(setupToken));
CountDownLatch latch = new CountDownLatch(1);
pairingTask.addObserver((state) -> {
if (state instanceof Paired) {
latch.countDown();
}
});
latch.await();
ownerProperties = component.getDatabaseComponent()
.transactionWithNullableResult(false, txn ->
component.getMailboxSettingsManager()
.getOwnMailboxProperties(txn)
);
}
@After
public void tearDown() {
mailbox.stopLifecycle();
}
private Identity setUp(MailboxIntegrationTestComponent device, String name)
throws Exception {
// Add an identity for the user
IdentityManager identityManager = device.getIdentityManager();
Identity identity = identityManager.createIdentity(name);
identityManager.registerIdentity(identity);
// Start the lifecycle manager
LifecycleManager lifecycleManager = device.getLifecycleManager();
lifecycleManager.startServices(getSecretKey());
lifecycleManager.waitForStartup();
// Check the initial conditions
ContactManager contactManager = device.getContactManager();
assertEquals(0, contactManager.getPendingContacts().size());
assertEquals(0, contactManager.getContacts().size());
return identity;
}
@Test
public void testUploadContacts() throws DbException {
int numContactsToAdd = 5;
List<ContactId> expectedContacts =
createContacts(component, identity, numContactsToAdd);
// Check for number of contacts on mailbox via API every 100ms
retryUntilSuccessOrTimeout(1000, 100, () -> {
try {
Collection<ContactId> contacts =
api.getContacts(ownerProperties);
if (contacts.size() == numContactsToAdd) {
assertEquals(expectedContacts, contacts);
return true;
}
} catch (IOException | ApiException e) {
LOG.log(WARNING, "Error while fetching contacts via API", e);
fail();
}
return false;
});
}
private List<ContactId> createContacts(
MailboxIntegrationTestComponent component, Identity local,
int numContacts) throws DbException {
List<ContactId> contactIds = new ArrayList<>();
ContactManager contactManager = component.getContactManager();
AuthorFactory authorFactory = component.getAuthorFactory();
for (int i = 0; i < numContacts; i++) {
Author remote = authorFactory.createLocalAuthor("Bob " + i);
contactIds.add(contactManager.addContact(remote, local.getId(),
rootKey, timestamp, true, true, true));
}
return contactIds;
}
}

View File

@@ -0,0 +1,14 @@
package org.briarproject.bramble.mailbox;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import static org.briarproject.bramble.mailbox.MailboxIntegrationTestUtils.URL_BASE;
@NotNullByDefault
class TestUrlConverter implements UrlConverter {
@Override
public String convertOnionToBaseUrl(String onion) {
return URL_BASE;
}
}

View File

@@ -0,0 +1,13 @@
<configuration>
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
</encoder>
</appender>
<root level="trace">
<appender-ref ref="STDOUT" />
</root>
<logger name="org.eclipse.jetty" level="INFO" />
<logger name="io.netty" level="INFO" />
<logger name="io.mockk" level="INFO" />
</configuration>