diff --git a/exonum-java-binding/core/src/main/java/com/exonum/binding/runtime/ReflectiveModuleSupplier.java b/exonum-java-binding/core/src/main/java/com/exonum/binding/runtime/ReflectiveModuleSupplier.java index 7d971cede0..ce2063a338 100644 --- a/exonum-java-binding/core/src/main/java/com/exonum/binding/runtime/ReflectiveModuleSupplier.java +++ b/exonum-java-binding/core/src/main/java/com/exonum/binding/runtime/ReflectiveModuleSupplier.java @@ -30,12 +30,18 @@ /** * A reflective supplier of service modules that instantiates them with a no-arg constructor. */ -final class ReflectiveModuleSupplier implements Supplier { +public final class ReflectiveModuleSupplier implements Supplier { private final Class moduleClass; private final MethodHandle moduleConstructor; - ReflectiveModuleSupplier(Class moduleClass) + /** + * Creates a module supplier for a given service module class. + * + * @throws NoSuchMethodException if the constructor of given service module class does not exist + * @throws IllegalAccessException if accessing the no-arg module constructor failed + */ + public ReflectiveModuleSupplier(Class moduleClass) throws NoSuchMethodException, IllegalAccessException { this.moduleClass = moduleClass; MethodHandles.Lookup lookup = MethodHandles.lookup(); diff --git a/exonum-java-binding/core/src/main/java/com/exonum/binding/service/adapters/UserServiceAdapter.java b/exonum-java-binding/core/src/main/java/com/exonum/binding/service/adapters/UserServiceAdapter.java index a4e0755d0a..c8f5897c9a 100644 --- a/exonum-java-binding/core/src/main/java/com/exonum/binding/service/adapters/UserServiceAdapter.java +++ b/exonum-java-binding/core/src/main/java/com/exonum/binding/service/adapters/UserServiceAdapter.java @@ -71,6 +71,10 @@ public String getName() { return service.getName(); } + public Service getService() { + return service; + } + /** * Converts a transaction messages into an executable transaction of this service. * diff --git a/exonum-java-binding/pom.xml b/exonum-java-binding/pom.xml index de3c6c73a6..8cc14c4331 100644 --- a/exonum-java-binding/pom.xml +++ b/exonum-java-binding/pom.xml @@ -68,6 +68,7 @@ service-archetype time-oracle packaging + testkit diff --git a/exonum-java-binding/testkit/pom.xml b/exonum-java-binding/testkit/pom.xml new file mode 100644 index 0000000000..42f621cde0 --- /dev/null +++ b/exonum-java-binding/testkit/pom.xml @@ -0,0 +1,74 @@ + + + 4.0.0 + + + com.exonum.binding + exonum-java-binding-parent + 0.6.0-SNAPSHOT + + + exonum-testkit + 0.6.0-SNAPSHOT + jar + + Exonum TestKit + https://exonum.com/doc/version/latest/get-started/test-service/ + + + ${project.parent.basedir}/core/rust/target/debug + + + + + + com.exonum.binding + exonum-java-binding-core + ${project.version} + + + + org.assertj + assertj-core + test + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + + + + maven-surefire-plugin + + + ${jacoco.args} + -Djava.library.path=${ejb-core.nativeLibPath} + ${java.vm.assertionFlag} + + + + + + org.apache.maven.plugins + maven-checkstyle-plugin + + ${project.parent.basedir}/../checkstyle.xml + + + + + org.apache.maven.plugins + maven-javadoc-plugin + + + + org.apache.maven.plugins + maven-source-plugin + + + + diff --git a/exonum-java-binding/testkit/src/main/java/com/exonum/binding/testkit/EmulatedNode.java b/exonum-java-binding/testkit/src/main/java/com/exonum/binding/testkit/EmulatedNode.java new file mode 100644 index 0000000000..feefffd311 --- /dev/null +++ b/exonum-java-binding/testkit/src/main/java/com/exonum/binding/testkit/EmulatedNode.java @@ -0,0 +1,69 @@ +/* + * Copyright 2019 The Exonum Team + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.exonum.binding.testkit; + +import com.exonum.binding.common.crypto.KeyPair; +import com.exonum.binding.service.Node; +import com.exonum.binding.transaction.RawTransaction; +import java.util.OptionalInt; + +/** + * Context of the TestKit emulated node. + */ +public class EmulatedNode { + + private final OptionalInt validatorId; + private final KeyPair serviceKeyPair; + + /** + * Creates a context of an emulated node. + * + * @param validatorId validator id of the validator node, less or equal to 0 in case of an + * auditor node + * @param serviceKeyPair service key pair of the node + */ + public EmulatedNode(int validatorId, KeyPair serviceKeyPair) { + this.validatorId = validatorId >= 0 + ? OptionalInt.of(validatorId) + : OptionalInt.empty(); + this.serviceKeyPair = serviceKeyPair; + } + + /** + * Returns a node type - either {@link EmulatedNodeType.VALIDATOR} or + * {@link EmulatedNodeType.AUDITOR}. + */ + public EmulatedNodeType getNodeType() { + return validatorId.isPresent() ? EmulatedNodeType.VALIDATOR : EmulatedNodeType.AUDITOR; + } + + /** + * Returns a validator id if this node is a validator or {@link OptionalInt.EMPTY} is this is an + * auditor node. + */ + public OptionalInt getValidatorId() { + return validatorId; + } + + /** + * Returns a service key pair of this node. This key pair is used to sign transactions + * {@linkplain Node#submitTransaction(RawTransaction)} produced} by the service itself. + */ + public KeyPair getServiceKeyPair() { + return serviceKeyPair; + } +} diff --git a/exonum-java-binding/testkit/src/main/java/com/exonum/binding/testkit/EmulatedNodeType.java b/exonum-java-binding/testkit/src/main/java/com/exonum/binding/testkit/EmulatedNodeType.java new file mode 100644 index 0000000000..3c50bb2652 --- /dev/null +++ b/exonum-java-binding/testkit/src/main/java/com/exonum/binding/testkit/EmulatedNodeType.java @@ -0,0 +1,28 @@ +/* + * Copyright 2019 The Exonum Team + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.exonum.binding.testkit; + +/** + * Type of the TestKit emulated node. + * + * @see Auditor Node + * Validator Node + */ +public enum EmulatedNodeType { + VALIDATOR, + AUDITOR +} diff --git a/exonum-java-binding/testkit/src/main/java/com/exonum/binding/testkit/FakeTimeProvider.java b/exonum-java-binding/testkit/src/main/java/com/exonum/binding/testkit/FakeTimeProvider.java new file mode 100644 index 0000000000..a9e618933a --- /dev/null +++ b/exonum-java-binding/testkit/src/main/java/com/exonum/binding/testkit/FakeTimeProvider.java @@ -0,0 +1,40 @@ +/* + * Copyright 2019 The Exonum Team + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.exonum.binding.testkit; + +import java.time.ZonedDateTime; + +// TODO: update Javadocs in P2 [ECR-3051] +public class FakeTimeProvider implements TimeProvider { + + public FakeTimeProvider create(ZonedDateTime time) { + throw new UnsupportedOperationException(); + } + + public void setTime(ZonedDateTime time) { + throw new UnsupportedOperationException(); + } + + public void addTime(ZonedDateTime time) { + throw new UnsupportedOperationException(); + } + + @Override + public ZonedDateTime getTime() { + throw new UnsupportedOperationException(); + } +} diff --git a/exonum-java-binding/testkit/src/main/java/com/exonum/binding/testkit/TestKit.java b/exonum-java-binding/testkit/src/main/java/com/exonum/binding/testkit/TestKit.java new file mode 100644 index 0000000000..845f6ed41f --- /dev/null +++ b/exonum-java-binding/testkit/src/main/java/com/exonum/binding/testkit/TestKit.java @@ -0,0 +1,240 @@ +/* + * Copyright 2019 The Exonum Team + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.exonum.binding.testkit; + +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkNotNull; +import static com.google.common.collect.Lists.asList; +import static java.util.Collections.singletonList; +import static java.util.stream.Collectors.toList; + +import com.exonum.binding.proxy.NativeHandle; +import com.exonum.binding.runtime.ReflectiveModuleSupplier; +import com.exonum.binding.service.BlockCommittedEvent; +import com.exonum.binding.service.Service; +import com.exonum.binding.service.ServiceModule; +import com.exonum.binding.service.adapters.UserServiceAdapter; +import com.google.common.annotations.VisibleForTesting; +import com.google.common.collect.Iterables; +import com.google.inject.Guice; +import com.google.inject.Injector; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.function.Supplier; +import javax.annotation.Nullable; + +/** + * TestKit for testing blockchain services. It offers simple network configuration emulation + * (with no real network setup). Although it is possible to add several validator nodes to this + * network, only one node will create the service instances and will execute their operations + * (e.g., {@link Service#afterCommit(BlockCommittedEvent)} method logic). + * + *

When TestKit is created, Exonum blockchain instance is initialized - service instances are + * {@linkplain UserServiceAdapter#initialize(long)} initialized} and genesis block is committed. + * Then the {@linkplain UserServiceAdapter#mountPublicApiHandler(long)} public API handlers} are + * created. + * + * @see TestKit documentation + */ +public final class TestKit { + + @VisibleForTesting + static final short MAX_SERVICE_NUMBER = 256; + private final Injector frameworkInjector = Guice.createInjector(new TestKitFrameworkModule()); + + private final NativeHandle nativeHandle; + private final Map services = new HashMap<>(); + + private TestKit(List> serviceModules, EmulatedNodeType nodeType, + short validatorCount, @Nullable TimeProvider timeProvider) { + List serviceAdapters = toUserServiceAdapters(serviceModules); + populateServiceMap(serviceAdapters); + boolean isAuditorNode = nodeType == EmulatedNodeType.AUDITOR; + UserServiceAdapter[] userServiceAdapters = serviceAdapters.toArray(new UserServiceAdapter[0]); + // TODO: fix after native implementation + nativeHandle = null; + // nativeHandle = new NativeHandle( + // nativeCreateTestKit(userServiceAdapters, isAuditorNode, validatorCount, timeProvider)); + } + + /** + * Returns a list of user service adapters created from given service modules. + */ + private List toUserServiceAdapters( + List> serviceModules) { + return serviceModules.stream() + .map(this::createUserServiceAdapter) + .collect(toList()); + } + + /** + * Instantiates a service given its module and wraps in a UserServiceAdapter for the native code. + */ + private UserServiceAdapter createUserServiceAdapter(Class moduleClass) { + try { + Supplier moduleSupplier = new ReflectiveModuleSupplier(moduleClass); + ServiceModule serviceModule = moduleSupplier.get(); + Injector serviceInjector = frameworkInjector.createChildInjector(serviceModule); + return serviceInjector.getInstance(UserServiceAdapter.class); + } catch (IllegalAccessException e) { + throw new IllegalArgumentException("Cannot access the no-arg module constructor", e); + } catch (NoSuchMethodException e) { + throw new IllegalArgumentException("No no-arg constructor", e); + } + } + + private void populateServiceMap(List serviceAdapters) { + for (UserServiceAdapter serviceAdapter: serviceAdapters) { + checkForDuplicateService(serviceAdapter); + services.put(serviceAdapter.getId(), serviceAdapter.getService()); + } + } + + private void checkForDuplicateService(UserServiceAdapter newService) { + short serviceId = newService.getId(); + checkArgument(!services.containsKey(serviceId), + "Service with id %s was added to the TestKit twice: %s and %s", + serviceId, services.get(serviceId), newService.getService()); + } + + /** + * Creates a TestKit network with a single validator node for a single service. + */ + public static TestKit forService(Class serviceModule) { + return new TestKit(singletonList(serviceModule), EmulatedNodeType.VALIDATOR, (short) 0, null); + } + + /** + * Returns an instance of a service with the given service id and service class. Only + * user-defined services can be requested, i.e., it is not possible to get an instance of a + * built-in service such as the time oracle. + * + * @return the service instance or null if there is no service with such id + * @throws IllegalArgumentException if the service with given id was not found or could not be + * cast to given class + */ + public T getService(short serviceId, Class serviceClass) { + Service service = services.get(serviceId); + checkArgument(service != null, "Service with given id=%s was not found", serviceId); + checkArgument(service.getClass().equals(serviceClass), + "Service (id=%s, class=%s) cannot be cast to %s", + serviceId, service.getClass().getCanonicalName(), serviceClass.getCanonicalName()); + return serviceClass.cast(service); + } + + private native long nativeCreateTestKit(UserServiceAdapter[] services, boolean auditor, + short withValidatorCount, TimeProvider timeProvider); + + private native long nativeCreateSnapshot(long nativeHandle); + + private native byte[] nativeCreateBlock(long nativeHandle); + + private native byte[] nativeCreateBlockWithTransactions(long nativeHandle, byte[][] transactions); + + private native EmulatedNode nativeGetEmulatedNode(long nativeHandle); + + /** + * Creates a new builder for the TestKit. + * + * @param nodeType type of the main TestKit node - either validator or auditor. Note that + * {@link Service#afterCommit(BlockCommittedEvent)} logic will only be called on the main TestKit + * node of this type + */ + public static Builder builder(EmulatedNodeType nodeType) { + checkNotNull(nodeType); + return new Builder(nodeType); + } + + /** + * Builder for the TestKit. + */ + public static final class Builder { + + private EmulatedNodeType nodeType; + private short validatorCount; + private List> services = new ArrayList<>(); + private TimeProvider timeProvider; + + private Builder(EmulatedNodeType nodeType) { + // TestKit network should have at least one validator node + if (nodeType == EmulatedNodeType.AUDITOR) { + validatorCount = 1; + } + this.nodeType = nodeType; + } + + /** + * Sets number of additional validator nodes in the TestKit network. Note that + * regardless of the configured number of validators, only a single service will be + * instantiated. + */ + public Builder withValidators(short validatorCount) { + this.validatorCount = validatorCount; + return this; + } + + /** + * Adds a service with which the TestKit would be instantiated. Several services can be added. + */ + public Builder withService(Class serviceModule) { + services.add(serviceModule); + return this; + } + + /** + * Adds services with which the TestKit would be instantiated. + */ + @SafeVarargs + public final Builder withServices(Class serviceModule, + Class... serviceModules) { + return withServices(asList(serviceModule, serviceModules)); + } + + /** + * Adds services with which the TestKit would be instantiated. + */ + public Builder withServices(Iterable> serviceModules) { + Iterables.addAll(services, serviceModules); + return this; + } + + /** + * If called, will create a TestKit with time service enabled. The time service will use the + * given {@linkplain TimeProvider} as a time source. + */ + public Builder withTimeService(TimeProvider timeProvider) { + this.timeProvider = timeProvider; + return this; + } + + /** + * Creates the TestKit instance. + */ + public TestKit build() { + checkCorrectServiceNumber(services.size()); + return new TestKit(services, nodeType, validatorCount, timeProvider); + } + + private void checkCorrectServiceNumber(int serviceCount) { + checkArgument(0 < serviceCount && serviceCount <= MAX_SERVICE_NUMBER, + "Number of services must be in range [1; %s], but was %s", + MAX_SERVICE_NUMBER, serviceCount); + } + } +} diff --git a/exonum-java-binding/testkit/src/main/java/com/exonum/binding/testkit/TestKitFrameworkModule.java b/exonum-java-binding/testkit/src/main/java/com/exonum/binding/testkit/TestKitFrameworkModule.java new file mode 100644 index 0000000000..f5b19475a0 --- /dev/null +++ b/exonum-java-binding/testkit/src/main/java/com/exonum/binding/testkit/TestKitFrameworkModule.java @@ -0,0 +1,33 @@ +/* + * Copyright 2019 The Exonum Team + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.exonum.binding.testkit; + +import com.exonum.binding.service.adapters.ViewFactory; +import com.exonum.binding.service.adapters.ViewProxyFactory; +import com.exonum.binding.transport.Server; +import com.google.inject.AbstractModule; +import com.google.inject.Singleton; + +class TestKitFrameworkModule extends AbstractModule { + + @Override + protected void configure() { + bind(Server.class).toProvider(Server::create).in(Singleton.class); + + bind(ViewFactory.class).toInstance(ViewProxyFactory.getInstance()); + } +} diff --git a/exonum-java-binding/testkit/src/main/java/com/exonum/binding/testkit/TimeProvider.java b/exonum-java-binding/testkit/src/main/java/com/exonum/binding/testkit/TimeProvider.java new file mode 100644 index 0000000000..b831807695 --- /dev/null +++ b/exonum-java-binding/testkit/src/main/java/com/exonum/binding/testkit/TimeProvider.java @@ -0,0 +1,24 @@ +/* + * Copyright 2019 The Exonum Team + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.exonum.binding.testkit; + +import java.time.ZonedDateTime; + +// TODO: update Javadocs in P2 [ECR-3051] +public interface TimeProvider { + ZonedDateTime getTime(); +} diff --git a/exonum-java-binding/testkit/src/test/java/com/exonum/binding/testkit/TestKitTest.java b/exonum-java-binding/testkit/src/test/java/com/exonum/binding/testkit/TestKitTest.java new file mode 100644 index 0000000000..c408f01fd6 --- /dev/null +++ b/exonum-java-binding/testkit/src/test/java/com/exonum/binding/testkit/TestKitTest.java @@ -0,0 +1,208 @@ +/* + * Copyright 2019 The Exonum Team + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.exonum.binding.testkit; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import com.exonum.binding.service.AbstractServiceModule; +import com.exonum.binding.service.Node; +import com.exonum.binding.service.Service; +import com.exonum.binding.service.ServiceModule; +import com.exonum.binding.service.TransactionConverter; +import com.exonum.binding.transaction.RawTransaction; +import com.exonum.binding.transaction.Transaction; +import com.google.common.collect.ImmutableList; +import com.google.inject.Singleton; +import io.vertx.ext.web.Router; +import java.util.ArrayList; +import java.util.List; +import org.junit.jupiter.api.Test; + +class TestKitTest { + + @Test + void createTestKitForSingleService() { + TestKit testKit = TestKit.forService(TestServiceModule.class); + Service service = testKit.getService(TestService.SERVICE_ID, TestService.class); + assertEquals(service.getId(), TestService.SERVICE_ID); + assertEquals(service.getName(), TestService.SERVICE_NAME); + } + + @Test + void createTestKitWithBuilderForSingleService() { + TestKit testKit = TestKit.builder(EmulatedNodeType.VALIDATOR) + .withService(TestServiceModule.class) + .build(); + Service service = testKit.getService(TestService.SERVICE_ID, TestService.class); + assertEquals(service.getId(), TestService.SERVICE_ID); + assertEquals(service.getName(), TestService.SERVICE_NAME); + } + + @Test + void createTestKitWithBuilderForMultipleSameServices() { + Class exceptionType = IllegalArgumentException.class; + List> serviceModules = ImmutableList.of(TestServiceModule.class, + TestServiceModule.class); + TestKit.Builder testKitBuilder = TestKit.builder(EmulatedNodeType.VALIDATOR) + .withServices(serviceModules); + assertThrows(exceptionType, testKitBuilder::build); + } + + @Test + void createTestKitWithBuilderForMultipleDifferentServices() { + TestKit testKit = TestKit.builder(EmulatedNodeType.VALIDATOR) + .withService(TestServiceModule.class) + .withService(TestServiceModule2.class) + .build(); + Service service = testKit.getService(TestService.SERVICE_ID, TestService.class); + Service service2 = testKit.getService(TestService2.SERVICE_ID, TestService2.class); + assertEquals(service.getId(), TestService.SERVICE_ID); + assertEquals(service.getName(), TestService.SERVICE_NAME); + assertEquals(service2.getId(), TestService2.SERVICE_ID); + assertEquals(service2.getName(), TestService2.SERVICE_NAME); + } + + @Test + void createTestKitWithBuilderForMultipleDifferentServicesVarargs() { + TestKit testKit = TestKit.builder(EmulatedNodeType.VALIDATOR) + .withServices(TestServiceModule.class, TestServiceModule2.class) + .build(); + Service service = testKit.getService(TestService.SERVICE_ID, TestService.class); + Service service2 = testKit.getService(TestService2.SERVICE_ID, TestService2.class); + assertEquals(service.getId(), TestService.SERVICE_ID); + assertEquals(service.getName(), TestService.SERVICE_NAME); + assertEquals(service2.getId(), TestService2.SERVICE_ID); + assertEquals(service2.getName(), TestService2.SERVICE_NAME); + } + + @Test + void requestWrongServiceClass() { + Class exceptionType = IllegalArgumentException.class; + TestKit testKit = TestKit.builder(EmulatedNodeType.VALIDATOR) + .withService(TestServiceModule.class) + .build(); + assertThrows(exceptionType, + () -> testKit.getService(TestService.SERVICE_ID, TestService2.class)); + } + + @Test + void requestWrongServiceId() { + Class exceptionType = IllegalArgumentException.class; + TestKit testKit = TestKit.builder(EmulatedNodeType.VALIDATOR) + .withService(TestServiceModule.class) + .build(); + assertThrows(exceptionType, () -> testKit.getService((short) -1, TestService2.class)); + } + + @Test + void createTestKitMoreThanMaxServiceNumber() { + Class exceptionType = IllegalArgumentException.class; + List> serviceModules = new ArrayList<>(); + for (int i = 0; i < TestKit.MAX_SERVICE_NUMBER + 1; i++) { + serviceModules.add(TestServiceModule.class); + } + TestKit.Builder testKitBuilder = TestKit.builder(EmulatedNodeType.VALIDATOR) + .withServices(serviceModules); + assertThrows(exceptionType, testKitBuilder::build); + } + + @Test + void createTestKitWithoutServices() { + Class exceptionType = IllegalArgumentException.class; + TestKit.Builder testKitBuilder = TestKit.builder(EmulatedNodeType.VALIDATOR); + assertThrows(exceptionType, testKitBuilder::build); + } + + public static final class TestServiceModule extends AbstractServiceModule { + + private static final TransactionConverter THROWING_TX_CONVERTER = (tx) -> { + throw new IllegalStateException("No transactions in this service: " + tx); + }; + + @Override + protected void configure() { + bind(Service.class).to(TestService.class).in(Singleton.class); + bind(TransactionConverter.class).toInstance(THROWING_TX_CONVERTER); + } + } + + public static final class TestServiceModule2 extends AbstractServiceModule { + + private static final TransactionConverter THROWING_TX_CONVERTER = (tx) -> { + throw new IllegalStateException("No transactions in this service: " + tx); + }; + + @Override + protected void configure() { + bind(Service.class).to(TestService2.class).in(Singleton.class); + bind(TransactionConverter.class).toInstance(THROWING_TX_CONVERTER); + } + } + + static final class TestService implements Service { + + static short SERVICE_ID = 46; + static String SERVICE_NAME = "Test service"; + + @Override + public short getId() { + return SERVICE_ID; + } + + @Override + public String getName() { + return SERVICE_NAME; + } + + @Override + public Transaction convertToTransaction(RawTransaction rawTransaction) { + throw new UnsupportedOperationException(); + } + + @Override + public void createPublicApiHandlers(Node node, Router router) { + // No-op: no handlers. + } + } + + static final class TestService2 implements Service { + + static short SERVICE_ID = 48; + static String SERVICE_NAME = "Test service 2"; + + @Override + public short getId() { + return SERVICE_ID; + } + + @Override + public String getName() { + return SERVICE_NAME; + } + + @Override + public Transaction convertToTransaction(RawTransaction rawTransaction) { + throw new UnsupportedOperationException(); + } + + @Override + public void createPublicApiHandlers(Node node, Router router) { + // No-op: no handlers. + } + } +}