diff --git a/Dockerfile b/Dockerfile index 90709a63b6..a59672f5ee 100644 --- a/Dockerfile +++ b/Dockerfile @@ -58,6 +58,10 @@ FROM eclipse-temurin:21.0.9_10-jre-noble AS smoketest-harness COPY --from=builder /app/server/setup/server-lib /opt/engine/server-lib COPY --from=builder /app/server/setup/extensions /opt/engine/extensions COPY --from=builder /app/server/setup/conf /opt/engine/conf +# The launcher resolves cli-lib/ relative to its working directory, so the CLI has +# to keep the layout it has in the distribution. +COPY --from=builder /app/server/setup/cli-lib /opt/engine/cli-lib +COPY --from=builder /app/server/setup/mirth-cli-launcher.jar /opt/engine/mirth-cli-launcher.jar COPY --from=builder /app/smoketest/build/install/smoketest-harness /harness ENTRYPOINT ["/bin/bash", "/harness/run-harness.sh"] diff --git a/ci/run-harness.sh b/ci/run-harness.sh index a1582525bb..202d40b208 100755 --- a/ci/run-harness.sh +++ b/ci/run-harness.sh @@ -20,6 +20,7 @@ java \ -Doie.baseUrl="$OIE_BASE_URL" \ -Doie.configuration="$OIE_CONFIGURATION" \ -Doie.password="$OIE_PASSWORD" \ + -Doie.cliHome="$ENGINE_HOME" \ ${OIE_DB_DRIVER:+-Doie.db.driver="$OIE_DB_DRIVER"} \ ${OIE_DB_URL:+-Doie.db.url="$OIE_DB_URL"} \ ${OIE_DB_USERNAME:+-Doie.db.username="$OIE_DB_USERNAME"} \ diff --git a/command/src/main/java/com/mirth/connect/cli/CommandLineInterface.java b/command/src/main/java/com/mirth/connect/cli/CommandLineInterface.java index 3f757d4ca1..483a48c5e6 100644 --- a/command/src/main/java/com/mirth/connect/cli/CommandLineInterface.java +++ b/command/src/main/java/com/mirth/connect/cli/CommandLineInterface.java @@ -216,7 +216,6 @@ private void runShell(String server, String user, String password, String script runConsole(); } client.logout(); - client.close(); out.println("Disconnected from server."); } catch (ClientException ce) { ce.printStackTrace(); @@ -224,6 +223,12 @@ private void runShell(String server, String user, String password, String script error("Could not load script file.", ioe); } catch (URISyntaxException e) { error("Invalid server address.", e); + } finally { + // The client's connection monitor is a non-daemon thread, so an unclosed + // client keeps the JVM alive instead of letting it exit. + if (client != null) { + client.close(); + } } } diff --git a/smoketest/src/test/java/org/openintegrationengine/smoketest/CommandLineClient.java b/smoketest/src/test/java/org/openintegrationengine/smoketest/CommandLineClient.java new file mode 100644 index 0000000000..a0e6c85be7 --- /dev/null +++ b/smoketest/src/test/java/org/openintegrationengine/smoketest/CommandLineClient.java @@ -0,0 +1,104 @@ +// SPDX-License-Identifier: MPL-2.0 +// SPDX-FileCopyrightText: 2026 Mitch Gaffigan + +package org.openintegrationengine.smoketest; + +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.concurrent.TimeUnit; + +/** + * Runs the packaged command-line client ({@code mirth-cli-launcher.jar}) as a child + * process. + * + *
The launcher resolves {@code cli-lib/} and {@code ./extensions} relative to its
+ * working directory and reads its log configuration from {@code conf/} via its manifest
+ * {@code Class-Path}, so it only runs from a staged distribution.
+ */
+final class CommandLineClient {
+
+ /** Distribution root; ci/run-harness.sh points this at the tree in the harness image. */
+ private static final Path HOME = Path.of(System.getProperty("oie.cliHome", "/opt/engine"));
+
+ private static final long TIMEOUT_SECONDS = HarnessConfig.TIMEOUT.toSeconds();
+
+ private CommandLineClient() {
+ }
+
+ /** Runs the CLI against the server under test, feeding it {@code statements} as a script. */
+ static Result runScript(String... statements) throws Exception {
+ return runScriptAgainst(HarnessConfig.BASE_URL, HarnessConfig.USERNAME, HarnessConfig.PASSWORD,
+ statements);
+ }
+
+ /** As {@link #runScript}, against an address and credentials of the caller's choosing. */
+ static Result runScriptAgainst(String address, String user, String password, String... statements)
+ throws Exception {
+ Path script = Files.createTempFile("oie-cli-", ".script");
+ try {
+ Files.writeString(script, String.join("\n", statements) + "\n", StandardCharsets.UTF_8);
+ return run("-a", address, "-u", user, "-p", password, "-s", script.toString());
+ } finally {
+ Files.deleteIfExists(script);
+ }
+ }
+
+ /** Runs the CLI with exactly {@code args}. */
+ static Result run(String... args) throws Exception {
+ Path launcher = HOME.resolve("mirth-cli-launcher.jar");
+ if (!Files.isRegularFile(launcher) || !Files.isRegularFile(HOME.resolve("cli-lib/mirth-cli.jar"))) {
+ throw new AssertionError("No command-line client staged at " + HOME
+ + "; the harness image is built to carry one (see the Dockerfile"
+ + " smoketest-harness target) and ci/run-harness.sh sets oie.cliHome.");
+ }
+
+ List Assertions are on output rather than on the exit code: the CLI exits 0 whether or
+ * not it could log in or run the statements it was given.
+ */
+@DisplayName("Command-line client")
+class CommandLineInterfaceTest {
+
+ @Test
+ @DisplayName("prints usage for -h without contacting a server")
+ void printsUsageForHelp() throws Exception {
+ CommandLineClient.Result result = CommandLineClient.run("-h");
+
+ assertEquals(0, result.exitCode(), () -> "-h should exit 0, got " + result);
+ assertTrue(result.output().contains("usage: Shell"), () -> "no usage text in " + result);
+
+ for (String option : List.of("-a