diff --git a/build.gradle b/build.gradle index fe016704..b35c4b19 100644 --- a/build.gradle +++ b/build.gradle @@ -58,6 +58,7 @@ configurations { compile.exclude group: "org.slf4j", module: "log4j-over-slf4j" // should be used only for generation of the stubs, the lib contains grpc classes compile.exclude group: "com.salesforce.servicelibs", module: "reactor-grpc" + } dependencies { @@ -101,6 +102,13 @@ dependencies { testImplementation libs.java.websocket testImplementation libs.equals.verifier testImplementation libs.groovy + testImplementation libs.bundles.testcontainers + testImplementation libs.bundles.junit + + testImplementation(libs.spring.boot.starter.test) { + exclude module: 'spring-boot-starter-logging' + } + testImplementation libs.grpc.testing detektPlugins libs.detekt.formatting } diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 4cb7d9bd..b3af0d0f 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -111,6 +111,14 @@ spring-security-core = { module = "org.springframework.security:spring-security- spring-security-web = { module = "org.springframework.security:spring-security-web", version.ref = "spring-security" } spring-security-config = { module = "org.springframework.security:spring-security-config", version.ref = "spring-security" } +spring-boot-starter-test = { module = "org.springframework.boot:spring-boot-starter-test", version.ref = "spring-boot" } + +testcontainers = "org.testcontainers:testcontainers:1.17.5" +testcontainers-ganache = "io.github.ganchix:testcontainers-java-module-ganache:0.0.4" + +junit-jupiter = "org.junit.jupiter:junit-jupiter:5.9.1" +assertj = "org.assertj:assertj-core:3.23.1" + [bundles] apache-commons = ["commons-io", "apache-commons-lang3", "apache-commons-collections4"] etherjar = ["etherjar-domain", "etherjar-hex", "etherjar-rpc-api", "etherjar-rpc-http", "etherjar-rpc-ws", "etherjar-tx", "etherjar-contract", "etherjar-erc20"] @@ -122,6 +130,9 @@ netty = ["netty-common", "netty-transport", "netty-handler-core", "netty-handler reactor = ["reactor-core", "reactor-netty", "reactor-extra", "reactor-kotlin"] slf4j = ["slf4j-api", "slf4j-jul", "slf4j-jcl", "log4j-slf4j"] spring-framework = ["spring-boot-starter", "spring-security-core", "spring-security-web", "spring-security-config"] +testcontainers = ["testcontainers", "testcontainers-ganache"] +junit = ["junit-jupiter", "assertj"] + [plugins] kotlin = { id = "org.jetbrains.kotlin.jvm", version.ref = "kotlin" } diff --git a/src/main/kotlin/io/emeraldpay/dshackle/Config.kt b/src/main/kotlin/io/emeraldpay/dshackle/Config.kt index 9d38961a..8ebd90fa 100644 --- a/src/main/kotlin/io/emeraldpay/dshackle/Config.kt +++ b/src/main/kotlin/io/emeraldpay/dshackle/Config.kt @@ -33,9 +33,11 @@ import org.springframework.boot.SpringApplication import org.springframework.context.ApplicationContext import org.springframework.context.annotation.Bean import org.springframework.context.annotation.Configuration +import org.springframework.context.annotation.Profile import org.springframework.core.env.Environment import org.springframework.scheduling.annotation.EnableAsync import org.springframework.scheduling.annotation.EnableScheduling +import org.springframework.util.ResourceUtils import reactor.core.scheduler.Scheduler import reactor.core.scheduler.Schedulers import java.io.File @@ -47,8 +49,8 @@ import kotlin.system.exitProcess @EnableScheduling @EnableAsync open class Config( - @Autowired private val env: Environment, - @Autowired private val ctx: ApplicationContext + private val env: Environment, + private val ctx: ApplicationContext ) { companion object { @@ -61,7 +63,12 @@ open class Config( private var configFilePath: File? = null init { - configFilePath = getConfigPath() + configFilePath = if (env.activeProfiles.contains("integration-test")) { + ResourceUtils.getFile("classpath:integration/dshackle.yaml") + } else { + getConfigPath() + } + Global.version = env.getProperty("version.app", Global.version).let { if (it.contains("SNAPSHOT")) { listOfNotNull(it, env.getProperty("version.commit")).joinToString("-") @@ -95,6 +102,7 @@ open class Config( } @Bean + @Profile("!integration-test") open fun mainConfig(@Autowired fileResolver: FileResolver): MainConfig { val f = configFilePath ?: throw IllegalStateException("Config path is not set") log.info("Using config: ${f.absolutePath}") diff --git a/src/test/kotlin/io/emeraldpay/dshackle/IntegrationTest.kt b/src/test/kotlin/io/emeraldpay/dshackle/IntegrationTest.kt new file mode 100644 index 00000000..ea89bbe8 --- /dev/null +++ b/src/test/kotlin/io/emeraldpay/dshackle/IntegrationTest.kt @@ -0,0 +1,111 @@ +package io.emeraldpay.dshackle + +import io.emeraldpay.api.proto.BlockchainGrpc +import io.emeraldpay.api.proto.BlockchainOuterClass +import io.emeraldpay.api.proto.Common.ChainRef +import io.emeraldpay.dshackle.config.MainConfig +import io.emeraldpay.dshackle.config.MainConfigReader +import io.emeraldpay.dshackle.config.UpstreamsConfig +import io.github.ganchix.ganache.Account +import io.github.ganchix.ganache.GanacheContainer +import io.grpc.BindableService +import io.grpc.inprocess.InProcessChannelBuilder +import io.grpc.inprocess.InProcessServerBuilder +import org.assertj.core.api.Assertions +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Test +import org.springframework.beans.factory.annotation.Autowired +import org.springframework.boot.test.context.SpringBootTest +import org.springframework.boot.test.context.TestConfiguration +import org.springframework.context.annotation.Bean +import org.springframework.context.annotation.Import +import org.springframework.context.annotation.Profile +import org.springframework.test.context.ActiveProfiles +import org.springframework.util.ResourceUtils +import java.math.BigInteger +import java.net.URI + +@SpringBootTest(properties = ["spring.main.allow-bean-definition-overriding=true"]) +@Import(Config::class) +@ActiveProfiles("integration-test") +class IntegrationTest { + + @Autowired + lateinit var services: List + + lateinit var stub: BlockchainGrpc.BlockchainBlockingStub + companion object { + + val PRIVATE_KEY_0 = "ae020c8ddb6fbc24e167b011666639d2ce3d4aa0d9c13d02d726d6865618a781" + val PRIVATE_KEY_1 = "81ad1ba5c4da47feb0f0163c0c61a66c4d0e6a66bd839827444b1e3362016140" + + var ganacheContainer: GanacheContainer<*> = GanacheContainer().apply { + withAccounts( + listOf( + Account.builder().privateKey(PRIVATE_KEY_0).balance(BigInteger.valueOf(2000000000000000000)).build(), + Account.builder().privateKey(PRIVATE_KEY_1).balance(BigInteger.valueOf(2000000000000000000)).build() + ) + ) + } + + init { + ganacheContainer.start() + } + } + + @BeforeEach + fun prepare() { + val serverName = InProcessServerBuilder.generateName() + val builder = InProcessServerBuilder.forName(serverName) + .directExecutor() + services.forEach { builder.addService(it) } + + val managedChannel = InProcessChannelBuilder.forName(serverName).directExecutor().build() + val server = builder.build() + server.start() + + stub = BlockchainGrpc.newBlockingStub(managedChannel) + } + + @Test + fun test() { + val result = stub.describe(BlockchainOuterClass.DescribeRequest.newBuilder().build()) + Assertions.assertThat(result.chainsCount).isEqualTo(1) + Assertions.assertThat(result.chainsList[0].chain).isEqualTo(ChainRef.CHAIN_ETHEREUM) + Assertions.assertThat(result.chainsList[0].nodesCount).isEqualTo(1) + } + + @TestConfiguration + open class Config { + @Bean + @Profile("integration-test") + open fun mainConfig(@Autowired fileResolver: FileResolver): MainConfig { + val reader = MainConfigReader(fileResolver) + val config = reader.read( + ResourceUtils.getFile("classpath:integration/dshackle.yaml") + .inputStream() + )!! + patch(config) + return config + } + + private fun patch(config: MainConfig) { + config.upstreams?.upstreams?.add( + UpstreamsConfig.Upstream().apply { + id = "ganache" + nodeId = 1 + chain = "ethereum" + connection = UpstreamsConfig.EthereumPosConnection().apply { + execution = UpstreamsConfig.EthereumConnection().apply { + rpc = UpstreamsConfig.HttpEndpoint( + URI.create( + "http://" + ganacheContainer.getHost() + ":" + ganacheContainer.getMappedPort(8545) + "/" + ) + ) + } + } + } + ) + } + } +} diff --git a/src/test/resources/integration/dshackle.yaml b/src/test/resources/integration/dshackle.yaml new file mode 100644 index 00000000..3938570b --- /dev/null +++ b/src/test/resources/integration/dshackle.yaml @@ -0,0 +1,21 @@ +version: v1 +host: 0.0.0.0 +port: 2450 +tls: + enabled: false +proxy: + host: 0.0.0.0 + port: 8550 + routes: + - id: eth + blockchain: ethereum + +monitoring: + enabled: false + extended: true + +signed-response: + enabled: false + +cluster: + upstreams: