package io.dropwizard.grpc.server.testing.junit; import static io.dropwizard.testing.ResourceHelpers.resourceFilePath; import static org.junit.Assert.assertEquals; import java.util.Collections; import java.util.concurrent.TimeUnit; import java.util.function.Function; import javax.net.ssl.SSLException; import io.dropwizard.cli.CheckCommand; import io.dropwizard.cli.Command; import io.dropwizard.setup.Bootstrap; import io.dropwizard.testing.DropwizardTestSupport; import io.dropwizard.util.Duration; import io.grpc.ManagedChannel; import io.grpc.ManagedChannelBuilder; import io.grpc.netty.GrpcSslContexts; import io.grpc.netty.NettyChannelBuilder; import io.netty.handler.ssl.SslContext; import net.sourceforge.argparse4j.inf.Namespace; /** * Utility class with convenience methods for testing. */ public final class Utils { /** * Creates a <code>ManagedChannel</code> connecting to the <b>plaintext</b> gRPC server in * <code>TestApplication</code> in <code>testSupport</code>. * * @param testSupport the already initialised (started) <code>DropwizardTestSupport</code> instance * @return the channel connecting to the server (to be used in a client) */ public static ManagedChannel createPlaintextChannel(final DropwizardTestSupport<TestConfiguration> testSupport) { final TestApplication application = testSupport.getApplication(); return ManagedChannelBuilder.forAddress("localhost", application.getServer().getPort()).usePlaintext() .build(); } /** * Creates a <code>ManagedChannel</code> connecting to an <b>encrypted</b> gRPC server in * <code>TestApplication</code> in <code>testSupport</code>. The certificate is taken from the * <code>GrpcServerFactory</code> in the configuration. * * @param testSupport the already initialised (started) <code>DropwizardTestSupport</code> instance * @return the channel connecting to the server (to be used in a client) */ public static ManagedChannel createClientChannelForEncryptedServer( final DropwizardTestSupport<TestConfiguration> testSupport) throws SSLException { final SslContext sslContext = GrpcSslContexts.forClient() .trustManager(testSupport.getConfiguration().getGrpcServerFactory().getCertChainFile().toFile()).build(); final TestApplication application = testSupport.getApplication(); return NettyChannelBuilder.forAddress("localhost", application.getServer().getPort()).sslContext(sslContext) .overrideAuthority("grpc-dropwizard.example.com").build(); } /** * Shuts down the given channel if not <code>null</code>, waiting for up to 1 second. * * @param channel the channel to shut down */ public static void shutdownChannel(final ManagedChannel channel) { if (channel != null) { channel.shutdown(); try { channel.awaitTermination(1, TimeUnit.SECONDS); } catch (final InterruptedException e) { // silently swallow exception } } } /** * Converts the given resource path string to a valid URI string. * * @param resourceClassPathLocation the path to the resource that is on the classpath * @return a valid <code>file://</code> URI string to the given resource path */ public static String getURIForResource(final String resourceClassPathLocation) { return "file:///" + resourceFilePath(resourceClassPathLocation).replaceAll("\\\\", "/"); } /** * Runs the check command for the {@link TestApplication} with the given yaml configuration. * * @param yamlConfig a <code>String</code> containing the yaml configuration * @return the TestApplication if successful * @throws Exception if the command failed */ @SuppressWarnings("unchecked") public static TestApplication runCheckCommandUsingConfig(final String yamlConfig) throws Exception { return runDropwizardCommandUsingConfig(yamlConfig, CheckCommand::new); } /** * Runs a command for the {@link TestApplication} with the given yaml configuration. * * @param yamlConfig a <code>String</code> containing the yaml configuration * @return the TestApplication if successful * @throws Exception if the command failed */ public static TestApplication runDropwizardCommandUsingConfig(final String yamlConfig, final Function<TestApplication, Command> commandInstantiator) throws Exception { final TestApplication application = new TestApplication(); final Bootstrap<TestConfiguration> bootstrap = new Bootstrap<>(application); bootstrap.setConfigurationSourceProvider(new StringConfigurationSourceProvider()); final Command command = commandInstantiator.apply(application); command.run(bootstrap, new Namespace(Collections.singletonMap("file", yamlConfig))); return application; } /** * Adds the given durations together. The implementation is not performant, use only for testing. * * @return a Duration that is the sum of <code>a</code> and <code>b</code>. If the unit of the 2 Durations is the * same, it will be used, otherwise they're converted to nanoseconds before summing */ public static Duration add(final Duration a, final Duration b) { return a.getUnit() == b.getUnit() ? Duration.parse( String.valueOf(a.getQuantity() + b.getQuantity()) + " " + a.getUnit().toString().toLowerCase()) : Duration.nanoseconds(a.toNanoseconds() + b.toNanoseconds()); } /** * Comparison is done in milliseconds. * * @param expected expected value * @param actual the value to check against <code>expected</code> * @param tolerance delta the maximum tolerance between <code>expected</code> and <code>actual</code> for which both * durations are still considered equal. */ public static void assertEqualsWithTolerance(final Duration expected, final Duration actual, final Duration tolerance) { assertEquals((double) expected.toMilliseconds(), actual.toMilliseconds(), tolerance.toMilliseconds()); } private Utils() { // private constructor to prevent instantiation } }