This is an automated email from the ASF dual-hosted git repository.
davsclaus pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/camel.git
The following commit(s) were added to refs/heads/main by this push:
new d4c36141a4c3 CAMEL-23688: camel-jbang - Tests for update run,
dependency copy, get error, init and doc (#27964)
d4c36141a4c3 is described below
commit d4c36141a4c3ddc3e4a507ece3a250a084084377
Author: Claus Ibsen <[email protected]>
AuthorDate: Fri Oct 16 21:07:58 2026 +0200
CAMEL-23688: camel-jbang - Tests for update run, dependency copy, get
error, init and doc (#27964)
Tests for commands that work on the project and the catalog, which had
little coverage (update run 0-5%, dependency copy 13%, init 39%, doc 33%,
get error 41%). They found these bugs, fixed here:
* camel dependency copy deleted the whole --output-directory before it
copied (--output-directory=. deleted the project); it deletes the JARs of
an earlier copy only. It exited with 0 when a dependency could not be
resolved or copied (now 1), and went on when --output-directory is a
file (now 2).
* camel update run: --download=false without --quarkusMavenPluginVersion
failed with an IllegalStateException that named an option that does not
exist (now a usage error), a version without recipes failed with a
NullPointerException, and the error without a project named the command
camel upgrade run. The project directory, the downloader and the Maven
invocation can be replaced in a test.
* camel get error --last showed the newest error of the integration with
the lowest pid, not the newest one; an --ago that is not a duration was
ignored, a --handled that is not true or false filtered out every error
(both now 2); the errors of --diagram went to stdout.
* camel init of a Java file in src/main/java failed with a
StringIndexOutOfBoundsException, and a folder such as src/main/javafx
gave a package.
* camel doc documented an EIP in JSON, but not as text ("not found"); it
does now, also with the prefix eip: for an EIP with the name of a
component, such as eip:log.
Co-authored-by: Claude Opus 5.5 (1M context) <[email protected]>
---
.../ROOT/pages/camel-4x-upgrade-guide-4_24.adoc | 12 +
.../ROOT/pages/camel-jbang-getting-started.adoc | 2 +
.../pages/jbang-commands/camel-jbang-commands.adoc | 2 +-
.../ROOT/pages/jbang-commands/camel-jbang-doc.adoc | 2 +-
.../META-INF/camel-jbang-commands-metadata.json | 2 +-
.../dsl/jbang/core/commands/DependencyCopy.java | 49 ++--
.../apache/camel/dsl/jbang/core/commands/Init.java | 23 +-
.../jbang/core/commands/catalog/CatalogDoc.java | 60 ++++-
.../dsl/jbang/core/commands/process/ListError.java | 66 ++---
.../jbang/core/commands/update/CamelUpdate.java | 5 +
.../core/commands/update/CamelUpdateMixin.java | 5 +-
.../dsl/jbang/core/commands/update/UpdateRun.java | 33 +--
.../jbang/core/commands/DependencyCopyTest.java | 149 ++++++++++++
.../camel/dsl/jbang/core/commands/InitTest.java | 111 +++++++++
.../core/commands/catalog/CatalogDocTest.java | 98 ++++++++
.../jbang/core/commands/process/ListErrorTest.java | 247 ++++++++++++++-----
.../jbang/core/commands/update/UpdateRunTest.java | 269 +++++++++++++++++++++
17 files changed, 996 insertions(+), 139 deletions(-)
diff --git
a/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_24.adoc
b/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_24.adoc
index 0cd7ed47f3a3..cf05e55fa98e 100644
--- a/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_24.adoc
+++ b/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_24.adoc
@@ -531,6 +531,18 @@ the integrations unsorted. A row without the value of the
key now comes first, i
of the CLI (`3.27.3.1` came after `3.27.4`), and a `-SNAPSHOT` always comes
before its release, as in Maven
(`4.24.10-SNAPSHOT` came after `4.24.10`).
+=== camel-jbang - camel dependency copy, update run and get error
+
+* `camel dependency copy` deletes the JAR files an earlier copy left in
`--output-directory`, and keeps its other files:
+ it deleted the whole directory (with `--output-directory=.` the project). It
exits with `1` when a dependency cannot
+ be resolved or copied (it printed the error and exited with `0`), and with
`2` when `--output-directory` is a file.
+* `camel update run --runtime=quarkus --download=false` without
`--quarkusMavenPluginVersion` exits with `2`, and a
+ version without upgrade recipes exits with `1`: both failed with an internal
error (`70`).
+* `camel get error --last` shows the newest error of all the integrations (it
showed the newest error of the integration
+ with the lowest pid). An `--ago` that is not a duration, and a `--handled`
that is not `true` or `false`, exit with
+ `2`: the first showed every error, the second none. The errors of
`--diagram` are printed on stderr, and a graphical
+ `--theme` in a terminal without graphics exits with `2`.
+
=== camel-jbang - the shared action file is deprecated for plugin commands
`CamelCommand.getActionFile` and `CamelCommand.getOutputFile`, the action and
output files that all the clients of an
diff --git
a/docs/user-manual/modules/ROOT/pages/camel-jbang-getting-started.adoc
b/docs/user-manual/modules/ROOT/pages/camel-jbang-getting-started.adoc
index 996ad00194d3..12b90c61a9b8 100644
--- a/docs/user-manual/modules/ROOT/pages/camel-jbang-getting-started.adoc
+++ b/docs/user-manual/modules/ROOT/pages/camel-jbang-getting-started.adoc
@@ -172,11 +172,13 @@ Show quick reference documentation (description + all
configuration options):
camel doc kafka
camel doc jackson
camel doc aws-kinesis-sink
+camel doc split
----
NOTE: This shows catalog-level documentation with option tables, not the full
website documentation.
If a component and data format share the same name, prefix with `dataformat:`
(e.g., `camel doc dataformat:thrift`).
+The same goes for an EIP with the name of a component, such as `camel doc
eip:log` for the Log EIP.
Open the online documentation in a browser with `--open-url`, or get just the
URL with `--url`:
diff --git
a/docs/user-manual/modules/ROOT/pages/jbang-commands/camel-jbang-commands.adoc
b/docs/user-manual/modules/ROOT/pages/jbang-commands/camel-jbang-commands.adoc
index ad5934d6c699..b8645eaee247 100644
---
a/docs/user-manual/modules/ROOT/pages/jbang-commands/camel-jbang-commands.adoc
+++
b/docs/user-manual/modules/ROOT/pages/jbang-commands/camel-jbang-commands.adoc
@@ -21,7 +21,7 @@ TIP: You can also use `camel --help` or `camel <command>
--help` to see availabl
| xref:jbang-commands/camel-jbang-dependency.adoc[camel dependency] | Displays
all Camel dependencies required to run
| xref:jbang-commands/camel-jbang-dev.adoc[camel dev] | Run in dev mode with
live reload
| xref:jbang-commands/camel-jbang-dirty.adoc[camel dirty] | Check if there are
dirty files from previous Camel runs that did not terminate gracefully
-| xref:jbang-commands/camel-jbang-doc.adoc[camel doc] | Shows documentation
for kamelet, component, and other Camel resources
+| xref:jbang-commands/camel-jbang-doc.adoc[camel doc] | Shows documentation
for kamelet, component, EIP, and other Camel resources
| xref:jbang-commands/camel-jbang-doctor.adoc[camel doctor] | Checks the
environment and reports potential issues
| xref:jbang-commands/camel-jbang-eval.adoc[camel eval] | Evaluate Camel
expressions and scripts
| xref:jbang-commands/camel-jbang-explain.adoc[camel explain] | Explain what a
Camel route does using AI/LLM
diff --git
a/docs/user-manual/modules/ROOT/pages/jbang-commands/camel-jbang-doc.adoc
b/docs/user-manual/modules/ROOT/pages/jbang-commands/camel-jbang-doc.adoc
index 3cdcab43ce71..786f9f4278ea 100644
--- a/docs/user-manual/modules/ROOT/pages/jbang-commands/camel-jbang-doc.adoc
+++ b/docs/user-manual/modules/ROOT/pages/jbang-commands/camel-jbang-doc.adoc
@@ -2,7 +2,7 @@
// AUTO-GENERATED by camel-package-maven-plugin - DO NOT EDIT THIS FILE
= camel doc
-Shows documentation for kamelet, component, and other Camel resources
+Shows documentation for kamelet, component, EIP, and other Camel resources
== Usage
diff --git
a/dsl/camel-jbang/camel-jbang-core/src/generated/resources/META-INF/camel-jbang-commands-metadata.json
b/dsl/camel-jbang/camel-jbang-core/src/generated/resources/META-INF/camel-jbang-commands-metadata.json
index ce92c5bcddae..dc38a95d8ecf 100644
---
a/dsl/camel-jbang/camel-jbang-core/src/generated/resources/META-INF/camel-jbang-commands-metadata.json
+++
b/dsl/camel-jbang/camel-jbang-core/src/generated/resources/META-INF/camel-jbang-commands-metadata.json
@@ -10,7 +10,7 @@
{ "name": "dependency", "fullName": "dependency", "description": "Displays
all Camel dependencies required to run", "sourceClass":
"org.apache.camel.dsl.jbang.core.commands.DependencyCommand", "options": [ {
"names": "-h,--help", "description": "Display the help and sub-commands",
"javaType": "boolean", "type": "boolean" } ], "subcommands": [ { "name":
"copy", "fullName": "dependency copy", "description": "Copies all Camel
dependencies required to run to a specific directory", "sourc [...]
{ "name": "dev", "fullName": "dev", "description": "Run in dev mode with
live reload", "sourceClass": "org.apache.camel.dsl.jbang.core.commands.Dev",
"options": [ { "names": "--background", "description": "Run in the background",
"defaultValue": "false", "javaType": "boolean", "type": "boolean" }, { "names":
"--background-wait", "description": "How long to wait for the integration in
the background to start, before returning, in millis or as a duration such as
2m (true waits 60 secon [...]
{ "name": "dirty", "fullName": "dirty", "description": "Check if there are
dirty files from previous Camel runs that did not terminate gracefully",
"sourceClass": "org.apache.camel.dsl.jbang.core.commands.process.Dirty",
"options": [ { "names": "--clean", "description": "Clean dirty files which are
no longer in use", "defaultValue": "false", "javaType": "boolean", "type":
"boolean" }, { "names": "-h,--help", "description": "Display the help and
sub-commands", "javaType": "boolean", " [...]
- { "name": "doc", "fullName": "doc", "description": "Shows documentation
for kamelet, component, and other Camel resources", "sourceClass":
"org.apache.camel.dsl.jbang.core.commands.catalog.CatalogDoc", "options": [ {
"names": "--camel-version", "description": "To use a different Camel version
than the default version", "javaType": "java.lang.String", "type": "string" },
{ "names": "--download", "description": "Whether to allow automatic downloading
JAR dependencies (over the internet [...]
+ { "name": "doc", "fullName": "doc", "description": "Shows documentation
for kamelet, component, EIP, and other Camel resources", "sourceClass":
"org.apache.camel.dsl.jbang.core.commands.catalog.CatalogDoc", "options": [ {
"names": "--camel-version", "description": "To use a different Camel version
than the default version", "javaType": "java.lang.String", "type": "string" },
{ "names": "--download", "description": "Whether to allow automatic downloading
JAR dependencies (over the int [...]
{ "name": "doctor", "fullName": "doctor", "description": "Checks the
environment and reports potential issues", "sourceClass":
"org.apache.camel.dsl.jbang.core.commands.Doctor", "options": [ { "names":
"--json", "description": "Output in JSON Format: the status (OK, WARN or FAIL)
of each check", "javaType": "boolean", "type": "boolean" }, { "names":
"-h,--help", "description": "Display the help and sub-commands", "javaType":
"boolean", "type": "boolean" } ] },
{ "name": "eval", "fullName": "eval", "description": "Evaluate Camel
expressions and scripts", "sourceClass":
"org.apache.camel.dsl.jbang.core.commands.EvalCommand", "options": [ { "names":
"-h,--help", "description": "Display the help and sub-commands", "javaType":
"boolean", "type": "boolean" } ], "subcommands": [ { "name": "expression",
"fullName": "eval expression", "description": "Evaluates Camel expression",
"sourceClass": "org.apache.camel.dsl.jbang.core.commands.action.EvalEx [...]
{ "name": "explain", "fullName": "explain", "description": "Explain what a
Camel route does using AI\/LLM", "sourceClass":
"org.apache.camel.dsl.jbang.core.commands.Explain", "options": [ { "names":
"--api-key", "description": "API key for authentication. Also reads
ANTHROPIC_API_KEY, OPENAI_API_KEY, WATSONX_APIKEY, or LLM_API_KEY env vars",
"javaType": "java.lang.String", "type": "string" }, { "names": "--api-type",
"description": "API type: 'ollama', 'openai' (OpenAI-compatible), ' [...]
diff --git
a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/DependencyCopy.java
b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/DependencyCopy.java
index 7f4d447b5973..d1ec34795362 100644
---
a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/DependencyCopy.java
+++
b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/DependencyCopy.java
@@ -17,18 +17,19 @@
package org.apache.camel.dsl.jbang.core.commands;
import java.io.IOException;
-import java.io.UncheckedIOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.List;
import java.util.Set;
+import java.util.stream.Stream;
+import org.apache.camel.dsl.jbang.core.common.CliException;
+import org.apache.camel.dsl.jbang.core.common.CliExitCode;
import org.apache.camel.tooling.maven.MavenArtifact;
import org.apache.camel.tooling.maven.MavenDownloader;
import org.apache.camel.tooling.maven.MavenGav;
import org.apache.camel.tooling.maven.MavenResolutionException;
-import org.apache.camel.util.FileUtil;
import picocli.CommandLine;
@CommandLine.Command(name = "copy",
@@ -49,27 +50,45 @@ public class DependencyCopy extends DependencyList {
private boolean outputDirectoryCreated = false;
+ // the artifacts that could not be resolved or copied
+ private int failures;
+
public DependencyCopy(CamelJBangMain main) {
super(main);
}
+ @Override
+ public Integer doCall() throws Exception {
+ Path dir = Paths.get(outputDirectory);
+ if (Files.exists(dir) && !Files.isDirectory(dir)) {
+ throw CliException.usage("--output-directory must be a directory,
was: " + outputDirectory);
+ }
+ int answer = super.doCall();
+ if (answer == 0 && failures > 0) {
+ // each artifact that failed is printed on stderr
+ return CliExitCode.FAILURE;
+ }
+ return answer;
+ }
+
private void createOutputDirectory() {
if (!outputDirectoryCreated) {
outputDirectoryCreated = true;
- Path outputDirectoryPath = Paths.get(outputDirectory);
- if (Files.exists(outputDirectoryPath)) {
- if (Files.isDirectory(outputDirectoryPath)) {
- FileUtil.removeDir(outputDirectoryPath.toFile());
- } else {
- printer().printErr("Error creating the output directory: "
+ outputDirectory
- + " is not a directory");
- return;
- }
- }
+ Path dir = Paths.get(outputDirectory);
try {
- Files.createDirectories(outputDirectoryPath);
+ if (Files.isDirectory(dir)) {
+ // the JARs of an earlier copy, which can be of other
versions, but not the other files of the
+ // directory: it is not always one that only holds the
dependencies, such as with
+ // --output-directory=.
+ try (Stream<Path> files = Files.list(dir)) {
+ for (Path jar : files.filter(f ->
f.getFileName().toString().endsWith(".jar")).toList()) {
+ Files.delete(jar);
+ }
+ }
+ }
+ Files.createDirectories(dir);
} catch (IOException e) {
- throw new UncheckedIOException("Error creating the output
directory: " + outputDirectory, e);
+ throw CliException.failure("Cannot create the output directory
" + outputDirectory + ": " + e.getMessage());
}
}
}
@@ -88,8 +107,10 @@ public class DependencyCopy extends DependencyList {
Files.copy(artifact.getFile().toPath(), target);
}
} catch (MavenResolutionException e) {
+ failures++;
printer().printErr("Error resolving the artifact: " + gav + " due
to: " + e.getMessage());
} catch (IOException e) {
+ failures++;
printer().printErr("Error copying the artifact: " + gav + " due
to: " + e.getMessage());
}
}
diff --git
a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/Init.java
b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/Init.java
index dd6fbf6df210..8bb3caf32a0c 100644
---
a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/Init.java
+++
b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/Init.java
@@ -16,7 +16,6 @@
*/
package org.apache.camel.dsl.jbang.core.commands;
-import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
@@ -240,19 +239,19 @@ public class Init extends CamelCommand {
/**
* @return The package declaration lines to insert at the beginning of the
file or empty string if no package found
*/
- private String computeJavaPackageDeclaration(Path targetPath) throws
IOException {
- String packageDeclaration = "";
- String canonicalPath =
targetPath.getParent().toAbsolutePath().normalize().toString();
- String srcMainJavaPath = Paths.get("src", "main", "java").toString();
- int index = canonicalPath.indexOf(srcMainJavaPath);
- if (index != -1) {
- String packagePath = canonicalPath.substring(index +
srcMainJavaPath.length() + 1);
- String packageName = packagePath.replace(File.separatorChar, '.');
- if (!packageName.isEmpty()) {
- packageDeclaration = "package " + packageName + ";\n\n";
+ private String computeJavaPackageDeclaration(Path targetPath) {
+ // the directories after src/main/java (as whole names: not
src/main/javafx, and none for src/main/java itself)
+ List<String> names = new ArrayList<>();
+ for (Path name : targetPath.toAbsolutePath().normalize().getParent()) {
+ names.add(name.toString());
+ }
+ for (int i = names.size() - 3; i >= 0; i--) {
+ if ("src".equals(names.get(i)) && "main".equals(names.get(i + 1))
&& "java".equals(names.get(i + 2))) {
+ String packageName = String.join(".", names.subList(i + 3,
names.size()));
+ return packageName.isEmpty() ? "" : "package " + packageName +
";\n\n";
}
}
- return packageDeclaration;
+ return "";
}
private int listTemplates() {
diff --git
a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/catalog/CatalogDoc.java
b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/catalog/CatalogDoc.java
index 5de4bbdd0b93..c3f250438043 100644
---
a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/catalog/CatalogDoc.java
+++
b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/catalog/CatalogDoc.java
@@ -48,6 +48,7 @@ import org.apache.camel.tooling.maven.MavenGav;
import org.apache.camel.tooling.model.BaseOptionModel;
import org.apache.camel.tooling.model.ComponentModel;
import org.apache.camel.tooling.model.DataFormatModel;
+import org.apache.camel.tooling.model.EipModel;
import org.apache.camel.tooling.model.JsonMapper;
import org.apache.camel.tooling.model.LanguageModel;
import org.apache.camel.tooling.model.MainModel;
@@ -59,7 +60,8 @@ import picocli.CommandLine;
import static
org.apache.camel.dsl.jbang.core.commands.catalog.CatalogBaseCommand.findComponentNames;
@CommandLine.Command(name = "doc",
- description = "Shows documentation for kamelet,
component, and other Camel resources", sortOptions = false,
+ description = "Shows documentation for kamelet,
component, EIP, and other Camel resources",
+ sortOptions = false,
showDefaultValues = true,
footer = {
"%nExamples:",
@@ -67,10 +69,12 @@ import static
org.apache.camel.dsl.jbang.core.commands.catalog.CatalogBaseComman
" camel doc timer",
" camel doc aws-s3-source",
" camel doc jsonpath",
+ " camel doc split",
" camel doc kafka --example" })
public class CatalogDoc extends CamelCommand {
- @CommandLine.Parameters(description = "Name of kamelet, component,
dataformat, or other Camel resource",
+ @CommandLine.Parameters(description = "Name of kamelet, component,
dataformat, language, EIP, or other Camel resource"
+ + " (prefix with eip: for an EIP
with the name of a component, such as eip:log)",
arity = "1")
String name;
@@ -192,6 +196,13 @@ public class CatalogDoc extends CamelCommand {
return 0;
}
}
+ if (prefix == null || "eip".equals(prefix)) {
+ EipModel em = catalog.eipModel(name);
+ if (em != null) {
+ docEip(em);
+ return 0;
+ }
+ }
// last, as the Kamelet catalog is downloaded (a component is
documented also offline)
if (prefix == null || "kamelet".equals(prefix)) {
KameletModel km = KameletCatalogHelper.loadKameletModel(name,
kameletsVersion, mavenResolver.repos());
@@ -230,6 +241,7 @@ public class CatalogDoc extends CamelCommand {
case "dataformat" ->
SuggestSimilarHelper.didYouMean(catalog.findDataFormatNames(), name);
case "language" ->
SuggestSimilarHelper.didYouMean(catalog.findLanguageNames(), name);
case "other" ->
SuggestSimilarHelper.didYouMean(catalog.findOtherNames(), name);
+ case "eip" ->
SuggestSimilarHelper.didYouMean(catalog.findModelNames(), name);
default -> List.of();
};
if (!suggestions.isEmpty()) {
@@ -709,6 +721,48 @@ public class CatalogDoc extends CamelCommand {
}
}
+ private void docEip(EipModel em) throws Exception {
+ String link = websiteLink("eip", em.getName(),
catalog.getCatalogVersion());
+ if (openUrl) {
+ if (link != null) {
+ Desktop.getDesktop().browse(new URI(link));
+ }
+ return;
+ }
+ if (url) {
+ if (link != null) {
+ printer().println(link);
+ }
+ return;
+ }
+
+ if (em.isDeprecated()) {
+ printer().printf("EIP Name: %s (deprecated)%n", em.getName());
+ } else {
+ printer().printf("EIP Name: %s%n", em.getName());
+ }
+ printer().printf("Since: %s%n",
fixQuarkusSince(em.getFirstVersionShort()));
+ printer().println("");
+ printer().printf("%s%n", em.getDescription());
+ printer().println("");
+ var filtered = filter(filter, em.getOptions());
+ var total1 = em.getOptions().size();
+ var total2 = filtered.size();
+ if (total1 == total2) {
+ printer().printf("The %s EIP supports (total: %s) options, which
are listed below.%n%n", em.getName(), total1);
+ } else {
+ printer().printf("The %s EIP supports (total: %s match-filter: %s)
options, which are listed below.%n%n",
+ em.getName(), total1, total2);
+ }
+ printer().println(optionTable(filtered));
+ printer().println("");
+
+ if (link != null) {
+ printer().println(link);
+ printer().println("");
+ }
+ }
+
private int[] docColumnWidths(int nameMax, int descMax, int defaultMax,
int typeMax) {
int tw = terminalWidth();
int borders = TerminalWidthHelper.fancyBorderOverhead(4);
@@ -825,6 +879,8 @@ public class CatalogDoc extends CamelCommand {
return
String.format("https://camel.apache.org/components/%s/others/%s.html", v, name);
} else if ("kamelet".equals(prefix)) {
return
String.format("https://camel.apache.org/camel-kamelets/%s/%s.html", v, name);
+ } else if ("eip".equals(prefix)) {
+ return
String.format("https://camel.apache.org/components/%s/eips/%s-eip.html", v,
name);
}
return null;
diff --git
a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/process/ListError.java
b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/process/ListError.java
index b83e6c4a31e7..e6914be3dffb 100644
---
a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/process/ListError.java
+++
b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/process/ListError.java
@@ -19,6 +19,7 @@ package org.apache.camel.dsl.jbang.core.commands.process;
import java.awt.image.BufferedImage;
import java.util.ArrayList;
import java.util.Arrays;
+import java.util.Comparator;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
@@ -39,6 +40,8 @@ import
org.apache.camel.diagram.RouteDiagramRenderer.DiagramColors;
import org.apache.camel.dsl.jbang.core.commands.CamelJBangMain;
import org.apache.camel.dsl.jbang.core.commands.action.MessageTableHelper;
import org.apache.camel.dsl.jbang.core.common.ActionClient;
+import org.apache.camel.dsl.jbang.core.common.CliException;
+import org.apache.camel.dsl.jbang.core.common.CliExitCode;
import org.apache.camel.dsl.jbang.core.common.CopyableRow;
import org.apache.camel.dsl.jbang.core.common.PidNameAgeCompletionCandidates;
import org.apache.camel.dsl.jbang.core.common.ProcessHelper;
@@ -129,6 +132,21 @@ public class ListError extends ProcessWatchCommand {
public Integer doProcessWatchCall() throws Exception {
List<Row> rows = new ArrayList<>();
+ // the options are wrong for every integration, so before reading them
+ Comparator<Row> order = ORDER.comparator(sort);
+ if (handled != null && !"true".equalsIgnoreCase(handled) &&
!"false".equalsIgnoreCase(handled)) {
+ throw CliException.usage("--handled must be true or false, was: "
+ handled);
+ }
+ long since = 0;
+ if (ago != null) {
+ try {
+ since = System.currentTimeMillis() -
TimeUtils.toMilliSeconds(ago);
+ } catch (IllegalArgumentException e) {
+ throw CliException.usage("--ago must be a duration such as
60s, 5m or 1h, was: " + ago);
+ }
+ }
+ long cutoff = since;
+
if (diagram) {
System.setProperty("java.awt.headless", "true");
last = true;
@@ -200,7 +218,7 @@ public class ListError extends ProcessWatchCommand {
}
// apply client-side filters
- if (matchesFilters(row)) {
+ if (matchesFilters(row, cutoff)) {
rows.add(row);
}
}
@@ -208,8 +226,8 @@ public class ListError extends ProcessWatchCommand {
}
});
- // sort rows
- rows.sort(ORDER.comparator(sort));
+ // the newest error of any integration, or the order of --sort
+ rows.sort(last ? ORDER.comparator("-age") : order);
// apply limit
List<Row> display = rows;
@@ -222,8 +240,8 @@ public class ListError extends ProcessWatchCommand {
if (diagram) {
Row row = display.get(0);
if (row.messageHistory == null || row.messageHistory.length ==
0) {
- printer().println("No message history available for this
error (enable message history in Camel)");
- return 1;
+ printer().printErr("No message history available for this
error (enable message history in Camel)");
+ return CliExitCode.FAILURE;
}
return doDiagramCall(Long.parseLong(row.pid), row);
}
@@ -329,8 +347,8 @@ public class ListError extends ProcessWatchCommand {
}
if (nodeIds.isEmpty()) {
- printer().println("No node IDs found in error message history");
- return 1;
+ printer().printErr("No node IDs found in error message history");
+ return CliExitCode.FAILURE;
}
RouteDiagramHelper.HighlightStyle hlStyle =
RouteDiagramHelper.HighlightStyle.FAIL;
@@ -345,8 +363,8 @@ public class ListError extends ProcessWatchCommand {
List<RouteInfo> routes = RouteDiagramHelper.parseRoutes(structureJson);
if (routes.isEmpty()) {
- printer().println("No routes found");
- return 1;
+ printer().printErr("No routes found");
+ return CliExitCode.FAILURE;
}
// add structural parent node IDs for each route that has highlighted
nodes
@@ -362,8 +380,8 @@ public class ListError extends ProcessWatchCommand {
= new RouteDiagramHelper.HighlightInfo(nodeIds, new
ArrayList<>(routeOrderSet), hlStyle);
routes = RouteDiagramHelper.filterAndOrderRoutes(routes,
highlightInfo);
if (routes.isEmpty()) {
- printer().println("No routes contain highlighted nodes from error
history");
- return 1;
+ printer().printErr("No routes contain highlighted nodes from error
history");
+ return CliExitCode.FAILURE;
}
// layout
@@ -393,16 +411,16 @@ public class ListError extends ProcessWatchCommand {
try {
image = renderer.renderDiagram(layoutRoutes, currentY, colors,
nodeIds, hlStyle);
} catch (IllegalStateException e) {
- printer().println(e.getMessage());
- return 1;
+ printer().printErr(e.getMessage());
+ return CliExitCode.FAILURE;
}
try (Terminal terminal =
TerminalBuilder.builder().system(true).build()) {
TerminalGraphics tg =
TerminalGraphicsManager.getBestProtocol(terminal).orElse(null);
if (tg == null) {
- printer().println(
+ printer().printErr(
"Terminal does not support graphics. Use
--theme=ascii or --theme=unicode for text output.");
- return 1;
+ return CliExitCode.USAGE;
}
TerminalGraphics.ImageOptions opts = new
TerminalGraphics.ImageOptions().preserveAspectRatio(true);
tg.displayImage(terminal, image, opts);
@@ -436,7 +454,7 @@ public class ListError extends ProcessWatchCommand {
}
}
- private boolean matchesFilters(Row row) {
+ private boolean matchesFilters(Row row, long cutoff) {
if (id != null && !id.equals(row.exchangeId)) {
return false;
}
@@ -447,21 +465,11 @@ public class ListError extends ProcessWatchCommand {
||
!row.exceptionType.toLowerCase().contains(exception.toLowerCase()))) {
return false;
}
- if (handled != null && !handled.equals(String.valueOf(row.handled))) {
+ if (handled != null &&
!handled.equalsIgnoreCase(String.valueOf(row.handled))) {
return false;
}
- if (ago != null) {
- try {
- long millis = TimeUtils.toMilliSeconds(ago);
- long cutoff = System.currentTimeMillis() - millis;
- if (row.timestamp < cutoff) {
- return false;
- }
- } catch (Exception e) {
- // ignore invalid ago value
- }
- }
- return true;
+ // older than --ago
+ return cutoff <= 0 || row.timestamp >= cutoff;
}
private String getAge(Row r) {
diff --git
a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/update/CamelUpdate.java
b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/update/CamelUpdate.java
index d3e927c7c2cf..20805b7fb8d5 100644
---
a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/update/CamelUpdate.java
+++
b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/update/CamelUpdate.java
@@ -53,6 +53,11 @@ public final class CamelUpdate implements Update {
List<Recipe> recipes;
MavenArtifact mavenArtifact
= downloader.downloadArtifact("org.apache.camel.upgrade",
getArtifactCoordinates(), updateMixin.version);
+ if (mavenArtifact == null) {
+ throw new CamelUpdateException(
+ "Cannot find the Camel upgrade recipes
org.apache.camel.upgrade:" + getArtifactCoordinates() + ":"
+ + updateMixin.version);
+ }
try {
recipes = getRecipesInJar(mavenArtifact.getFile().toPath());
diff --git
a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/update/CamelUpdateMixin.java
b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/update/CamelUpdateMixin.java
index a3e0ee52d993..443f07ea2610 100644
---
a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/update/CamelUpdateMixin.java
+++
b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/update/CamelUpdateMixin.java
@@ -20,6 +20,7 @@ import java.util.List;
import org.apache.camel.dsl.jbang.core.commands.MavenResolverMixin;
import org.apache.camel.dsl.jbang.core.commands.QuarkusExtensionRegistryMixin;
+import org.apache.camel.dsl.jbang.core.common.CliException;
import org.apache.camel.dsl.jbang.core.common.QuarkusHelper;
import org.apache.camel.dsl.jbang.core.common.QuarkusHelper.QuarkusPlatformBom;
import org.apache.camel.dsl.jbang.core.common.RuntimeCompletionCandidates;
@@ -100,8 +101,8 @@ public class CamelUpdateMixin {
return MavenGav.fromCoordinates(quarkusMavenPluginGroupId,
"quarkus-maven-plugin", quarkusMavenPluginVersion, "jar",
null);
} else if (!mavenResolver.download()) {
- throw new IllegalStateException(
- "Could not find default Quarkus Platform Maven BOM
coordinates without --download true. You may want to either pass --download
true or set --quarkus-version");
+ throw CliException.usage("--quarkusMavenPluginVersion is required
with --download=false, as the version of "
+ + "the Quarkus Maven plugin is looked up
in the Quarkus Extension Registry");
} else {
QuarkusPlatformBom result
= QuarkusHelper.findQuarkusPlatformBom(
diff --git
a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/update/UpdateRun.java
b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/update/UpdateRun.java
index 561680fda17f..d6fb46faa642 100644
---
a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/update/UpdateRun.java
+++
b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/update/UpdateRun.java
@@ -24,7 +24,6 @@ import java.nio.file.Path;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;
-import java.util.stream.Stream;
import org.apache.camel.dsl.jbang.core.commands.CamelCommand;
import org.apache.camel.dsl.jbang.core.commands.CamelJBangMain;
@@ -49,6 +48,9 @@ public class UpdateRun extends CamelCommand {
@CommandLine.Mixin
private CamelUpdateMixin updateMixin;
+ // the directory of the Maven project to update
+ Path projectDir = Path.of(".");
+
public UpdateRun(CamelJBangMain main) {
super(main);
}
@@ -63,18 +65,14 @@ public class UpdateRun extends CamelCommand {
*/
@Override
public Integer doCall() throws Exception {
- try (Stream<Path> files = Files.list(Path.of("."))) {
- // Check if the current directory contains a Maven project (i.e.,
a pom.xml file)
- if (files.noneMatch(f ->
f.getFileName().toString().equals("pom.xml"))) {
- printer().printErr("No Maven Project found in the current
directory, " +
- "please execute camel upgrade run command
in the directory containing the Maven project to update");
-
- return CliExitCode.NOT_FOUND;
- }
+ if (!Files.isRegularFile(projectDir.resolve("pom.xml"))) {
+ printer().printErr("No Maven project found in the current
directory: run camel update run in the directory "
+ + "with the pom.xml of the project to update");
+ return CliExitCode.NOT_FOUND;
}
List<String> command = new ArrayList<>();
- try (MavenDependencyDownloader downloader = new
MavenDependencyDownloader();) {
+ try (MavenDependencyDownloader downloader = createDownloader()) {
downloader.setRepositories(updateMixin.mavenResolver.repos());
downloader.start();
@@ -94,8 +92,8 @@ public class UpdateRun extends CamelCommand {
return CliExitCode.FAILURE;
} catch (DownloadException e) {
- printer().printErr(String.format("Cannot find Camel Upgrade
Recipes %s:%s:%s",
- "org.apache.camel.upgrade",
update.getArtifactCoordinates(), updateMixin.version));
+ printer().printErr("Cannot download what the update to Camel "
+ updateMixin.version + " needs: "
+ + e.getMessage());
return CliExitCode.FAILURE;
}
@@ -104,6 +102,13 @@ public class UpdateRun extends CamelCommand {
return executeCommand(command);
}
+ /**
+ * The downloader of the upgrade recipes and versions.
+ */
+ protected MavenDependencyDownloader createDownloader() {
+ return new MavenDependencyDownloader();
+ }
+
/**
* Executes a shell command and prints its output.
*
@@ -112,8 +117,8 @@ public class UpdateRun extends CamelCommand {
* @throws IOException if an I/O error occurs
* @throws InterruptedException if the command execution is interrupted
*/
- private int executeCommand(List<String> command) throws IOException,
InterruptedException {
- ProcessBuilder pb = new ProcessBuilder(command);
+ protected int executeCommand(List<String> command) throws IOException,
InterruptedException {
+ ProcessBuilder pb = new
ProcessBuilder(command).directory(projectDir.toFile());
Process p = pb.redirectErrorStream(true)
.start();
diff --git
a/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/DependencyCopyTest.java
b/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/DependencyCopyTest.java
new file mode 100644
index 000000000000..d50387268eb9
--- /dev/null
+++
b/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/DependencyCopyTest.java
@@ -0,0 +1,149 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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 org.apache.camel.dsl.jbang.core.commands;
+
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.List;
+import java.util.stream.Stream;
+
+import org.apache.camel.dsl.jbang.core.common.CliException;
+import org.apache.camel.dsl.jbang.core.common.CliExitCode;
+import org.apache.camel.tooling.maven.MavenArtifact;
+import org.apache.camel.tooling.maven.MavenDownloader;
+import org.apache.camel.tooling.maven.MavenGav;
+import org.apache.camel.tooling.maven.MavenResolutionException;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.io.TempDir;
+import picocli.CommandLine;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.anyList;
+import static org.mockito.ArgumentMatchers.anySet;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.spy;
+
+/**
+ * camel dependency copy: the JARs of the dependencies of the integration,
copied to a directory. The dependencies are
+ * not downloaded: each resolves to a JAR of its own.
+ */
+class DependencyCopyTest extends CamelCommandBaseTestSupport {
+
+ @TempDir
+ Path work;
+
+ @TempDir
+ Path repo;
+
+ @TempDir
+ Path lib;
+
+ @Test
+ void copiesTheJarsOfTheDependencies() throws Exception {
+ assertThat(command(resolver(null)).doCall()).isZero();
+
+ assertThat(lib.resolve("camel-main-4.11.0.jar")).exists();
+ assertThat(jars()).hasSizeGreaterThan(1).allMatch(name ->
name.endsWith("-4.11.0.jar"));
+ }
+
+ @Test
+ void replacesTheJarsOfAnEarlierCopyAndKeepsTheOtherFiles() throws
Exception {
+ Files.writeString(lib.resolve("camel-main-4.10.0.jar"), "older");
+ Files.writeString(lib.resolve("README.txt"), "my notes");
+ Files.createDirectories(lib.resolve("config"));
+
+ assertThat(command(resolver(null)).doCall()).isZero();
+
+ assertThat(lib.resolve("camel-main-4.10.0.jar")).doesNotExist();
+ assertThat(lib.resolve("camel-main-4.11.0.jar")).exists();
+ assertThat(lib.resolve("README.txt")).hasContent("my notes");
+ assertThat(lib.resolve("config")).isDirectory();
+ }
+
+ @Test
+ void anArtifactThatCannotBeResolvedIsAFailure() throws Exception {
+
assertThat(command(resolver("camel-main")).doCall()).isEqualTo(CliExitCode.FAILURE);
+
+ assertThat(printer.getStderr())
+ .contains("Error resolving the artifact:
org.apache.camel:camel-main:4.11.0 due to: not in the repository");
+ // the other dependencies are copied
+
assertThat(jars()).isNotEmpty().doesNotContain("camel-main-4.11.0.jar");
+ }
+
+ @Test
+ void outputDirectoryThatIsAFileIsAUsageError() throws Exception {
+ Path file = Files.writeString(work.resolve("lib"), "not a directory");
+ DependencyCopy command = command(resolver(null));
+ command.outputDirectory = file.toString();
+
+ assertThatThrownBy(command::doCall)
+ .isInstanceOfSatisfying(CliException.class,
+ e ->
assertThat(e.getExitCode()).isEqualTo(CliExitCode.USAGE))
+ .hasMessage("--output-directory must be a directory, was: " +
file);
+ }
+
+ @Test
+ void theOutputDirectoryIsLibByDefault() {
+ DependencyCopy command = new DependencyCopy(new
CamelJBangMain().withPrinter(printer));
+
+ CommandLine.populateCommand(command);
+
+ assertThat(command.outputDirectory).isEqualTo("lib");
+ }
+
+ private DependencyCopy command(MavenDownloader downloader) {
+ DependencyCopy command = new DependencyCopy(new
CamelJBangMain().withPrinter(printer));
+ CommandLine.populateCommand(command, "--gav=examples:route:1.0.0",
"--dir=" + work, "--quiet",
+ "--runtime=camel-main", "--camel-version=4.11.0",
"--quarkus-version=3.22.2",
+ CamelCommandBaseTestSupport.quarkusExtRegistry(),
"--output-directory=" + lib);
+ command.files = List.of("classpath:route.yaml");
+ command.mavenResolver = new MavenResolverMixin() {
+ @Override
+ public MavenDownloader downloader() {
+ return downloader;
+ }
+ };
+ return command;
+ }
+
+ // resolves each dependency to a JAR of its own, except the one with the
artifact id that is missing (the export
+ // before the copy resolves the rest as usual)
+ private MavenDownloader resolver(String missing) throws Exception {
+ MavenDownloader downloader = spy(new
MavenResolverMixin().downloader());
+ doAnswer(inv -> {
+ List<String> gavs = inv.getArgument(0);
+ MavenGav gav = MavenGav.parseGav(gavs.get(0));
+ if (gav.getArtifactId().equals(missing)) {
+ throw new MavenResolutionException("not in the repository");
+ }
+ Path jar = repo.resolve(gav.getArtifactId() + "-" +
gav.getVersion() + ".jar");
+ if (!Files.exists(jar)) {
+ Files.writeString(jar, gav.toString());
+ }
+ return List.of(new MavenArtifact(gav, jar.toFile()));
+ }).when(downloader).resolveArtifacts(anyList(), anySet(),
anyBoolean(), anyBoolean());
+ return downloader;
+ }
+
+ private List<String> jars() throws Exception {
+ try (Stream<Path> files = Files.list(lib)) {
+ return files.map(f -> f.getFileName().toString()).filter(name ->
name.endsWith(".jar")).toList();
+ }
+ }
+}
diff --git
a/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/InitTest.java
b/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/InitTest.java
index a7a1e8cf756f..5ae9694efb97 100644
---
a/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/InitTest.java
+++
b/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/InitTest.java
@@ -125,6 +125,117 @@ class InitTest {
Files.delete(f);
}
+ @Test
+ void initJavaInTheSourceFolderHasNoPackage() throws Exception {
+ Path sourceFolder =
Files.createDirectories(workingDir.resolve("src/main/java"));
+
+ Init initCommand = new Init(new CamelJBangMain());
+ CommandLine.populateCommand(initCommand, "MyRoute.java", "--dir=" +
sourceFolder);
+
+ int exit = initCommand.doCall();
+
+ assertEquals(0, exit);
+ List<String> lines =
Files.readAllLines(sourceFolder.resolve("MyRoute.java"));
+ assertEquals("import org.apache.camel.builder.RouteBuilder;",
lines.get(0));
+ }
+
+ @Test
+ void initJavaInAFolderThatOnlyStartsLikeTheSourceFolderHasNoPackage()
throws Exception {
+ Path folder =
Files.createDirectories(workingDir.resolve("src/main/javafx/demo"));
+
+ Init initCommand = new Init(new CamelJBangMain());
+ CommandLine.populateCommand(initCommand, "MyRoute.java", "--dir=" +
folder);
+
+ int exit = initCommand.doCall();
+
+ assertEquals(0, exit);
+ List<String> lines =
Files.readAllLines(folder.resolve("MyRoute.java"));
+ assertEquals("import org.apache.camel.builder.RouteBuilder;",
lines.get(0));
+ }
+
+ @Test
+ void initXml() throws Exception {
+ Init initCommand = new Init(new CamelJBangMain());
+ CommandLine.populateCommand(initCommand, "my.camel.xml", "--dir=" +
workingDir);
+
+ int exit = initCommand.doCall();
+
+ assertEquals(0, exit);
+ String content = Files.readString(workingDir.resolve("my.camel.xml"));
+ assertTrue(content.contains("<route"), content);
+ }
+
+ @Test
+ void initKamelets() throws Exception {
+ for (String name : List.of("my-source.kamelet.yaml",
"my-sink.kamelet.yaml", "my-action.kamelet.yaml")) {
+ Init initCommand = new Init(new CamelJBangMain());
+ CommandLine.populateCommand(initCommand, name, "--dir=" +
workingDir);
+
+ int exit = initCommand.doCall();
+
+ assertEquals(0, exit);
+ String content = Files.readString(workingDir.resolve(name));
+ assertTrue(content.contains("kind: Kamelet"), content);
+ // named after the file
+ assertTrue(content.contains("name: " + name.substring(0,
name.indexOf('.'))), content);
+ }
+
assertTrue(Files.readString(workingDir.resolve("my-source.kamelet.yaml"))
+ .contains("camel.apache.org/kamelet.type: \"source\""));
+ assertTrue(Files.readString(workingDir.resolve("my-sink.kamelet.yaml"))
+ .contains("camel.apache.org/kamelet.type: \"sink\""));
+ }
+
+ @Test
+ void initPipe() throws Exception {
+ Init initCommand = new Init(new CamelJBangMain());
+ CommandLine.populateCommand(initCommand, "my.yaml", "--pipe", "--dir="
+ workingDir);
+
+ int exit = initCommand.doCall();
+
+ assertEquals(0, exit);
+
assertTrue(Files.readString(workingDir.resolve("my.yaml")).contains("kind:
Pipe"));
+ }
+
+ @Test
+ void initCleansTheDirectoryFirst() throws Exception {
+ Path dir = Files.createDirectories(workingDir.resolve("project"));
+ Path old = Files.writeString(dir.resolve("old.yaml"), "- route: {}");
+
+ Init initCommand = new Init(new CamelJBangMain());
+ CommandLine.populateCommand(initCommand, "my.camel.yaml", "--dir=" +
dir, "--clean-dir");
+
+ int exit = initCommand.doCall();
+
+ assertEquals(0, exit);
+ assertFalse(Files.exists(old), "the directory is cleaned first");
+ assertTrue(Files.exists(dir.resolve("my.camel.yaml")));
+ }
+
+ @Test
+ void initUnsupportedFileTypeIsAUsageError() throws Exception {
+ StringPrinter printer = new StringPrinter();
+ Init initCommand = new Init(new CamelJBangMain().withPrinter(printer));
+ CommandLine.populateCommand(initCommand, "notes.txt", "--dir=" +
workingDir);
+
+ int exit = initCommand.doCall();
+
+ assertEquals(2, exit);
+ assertTrue(printer.getStderr().contains("Unsupported file type: txt"),
printer.getStderr());
+ assertFalse(Files.exists(workingDir.resolve("notes.txt")));
+ }
+
+ @Test
+ void initFromKameletNeedsAKameletFile() throws Exception {
+ StringPrinter printer = new StringPrinter();
+ Init initCommand = new Init(new CamelJBangMain().withPrinter(printer));
+ CommandLine.populateCommand(initCommand, "my.yaml",
"--from-kamelet=timer-source", "--dir=" + workingDir);
+
+ int exit = initCommand.doCall();
+
+ assertEquals(2, exit);
+ assertTrue(printer.getStderr().contains("file must have extension
.kamelet.yaml"), printer.getStderr());
+ }
+
@Test
void initListTemplates() throws Exception {
StringPrinter printer = new StringPrinter();
diff --git
a/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/catalog/CatalogDocTest.java
b/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/catalog/CatalogDocTest.java
index 209321c9260d..30e04440c94a 100644
---
a/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/catalog/CatalogDocTest.java
+++
b/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/catalog/CatalogDocTest.java
@@ -21,6 +21,7 @@ import
org.apache.camel.dsl.jbang.core.commands.CamelJBangMain;
import org.apache.camel.dsl.jbang.core.commands.MavenResolverMixin;
import org.apache.camel.dsl.jbang.core.common.CliException;
import org.apache.camel.dsl.jbang.core.common.CliExitCode;
+import org.apache.camel.dsl.jbang.core.common.StringPrinter;
import org.apache.camel.util.json.JsonObject;
import org.apache.camel.util.json.Jsoner;
import org.junit.jupiter.api.Assertions;
@@ -116,6 +117,103 @@ class CatalogDocTest extends CamelCommandBaseTestSupport {
Assertions.assertEquals("timer", doc.getString("name"));
}
+ @Test
+ public void shouldDocumentAnEip() throws Exception {
+ CatalogDoc command = createCommand("split", false);
+
+ int exit = command.doCall();
+
+ Assertions.assertEquals(0, exit);
+ String output = printer.getOutput();
+ Assertions.assertTrue(output.startsWith("EIP Name: split"), output);
+ Assertions.assertTrue(output.contains("The split EIP supports (total:
"), output);
+ Assertions.assertTrue(output.contains("aggregationStrategy"), output);
+ Assertions.assertTrue(output.contains("/eips/split-eip.html"), output);
+ }
+
+ @Test
+ public void shouldDocumentTheEipWithTheNameOfAComponentWithThePrefix()
throws Exception {
+ CatalogDoc command = createCommand("log", false);
+ Assertions.assertEquals(0, command.doCall());
+ Assertions.assertTrue(printer.getOutput().startsWith("Component Name:
log"), printer.getOutput());
+
+ printer = new StringPrinter();
+ command = createCommand("eip:log", false);
+ Assertions.assertEquals(0, command.doCall());
+ Assertions.assertTrue(printer.getOutput().startsWith("EIP Name: log"),
printer.getOutput());
+ }
+
+ @Test
+ public void shouldPrintTheLinkOfAnEip() throws Exception {
+ CatalogDoc command = createCommand("split", false);
+ command.url = true;
+
+ Assertions.assertEquals(0, command.doCall());
+
+ String output = printer.getOutput();
+
Assertions.assertTrue(output.matches("https://camel\\.apache\\.org/components/[^/]+/eips/split-eip\\.html"),
+ output);
+ }
+
+ @Test
+ public void shouldSuggestASimilarEip() throws Exception {
+ CatalogDoc command = createCommand("eip:spllit", false);
+
+ CliException e = Assertions.assertThrows(CliException.class,
command::doCall);
+
+ Assertions.assertEquals(CliExitCode.NOT_FOUND, e.getExitCode());
+ Assertions.assertTrue(e.getMessage().startsWith("Camel eip: spllit not
found. Did you mean? split"),
+ e.getMessage());
+ }
+
+ @Test
+ public void shouldPrintEipDocAsJson() throws Exception {
+ CatalogDoc command = createCommand("split", false);
+ command.jsonOutput = true;
+
+ Assertions.assertEquals(0, command.doCall());
+
+ JsonObject doc = Jsoner.deserialize(printer.getOutput(), (JsonObject)
null);
+ Assertions.assertNotNull(doc, printer.getOutput());
+ Assertions.assertEquals("eip", doc.getString("kind"));
+ }
+
+ @Test
+ public void shouldDocumentADataFormatAndALanguage() throws Exception {
+ CatalogDoc command = createCommand("jackson", false);
+ Assertions.assertEquals(0, command.doCall());
+ Assertions.assertTrue(printer.getOutput().startsWith("Dataformat Name:
jackson"), printer.getOutput());
+
Assertions.assertTrue(printer.getOutput().contains("<artifactId>camel-jackson</artifactId>"),
+ printer.getOutput());
+
+ printer = new StringPrinter();
+ command = createCommand("language:simple", false);
+ Assertions.assertEquals(0, command.doCall());
+ Assertions.assertTrue(printer.getOutput().startsWith("Language Name:
simple"), printer.getOutput());
+ }
+
+ @Test
+ public void shouldFilterTheOptions() throws Exception {
+ CatalogDoc command = createCommand("timer", false);
+ command.filter = "delay";
+
+ Assertions.assertEquals(0, command.doCall());
+
+ String output = printer.getOutput();
+ Assertions.assertTrue(output.contains("match-filter: "), output);
+ Assertions.assertTrue(output.contains("delay"), output);
+ Assertions.assertFalse(output.contains("repeatCount"), output);
+ }
+
+ @Test
+ public void shouldDocumentCamelMain() throws Exception {
+ CatalogDoc command = createCommand("main", false);
+
+ Assertions.assertEquals(0, command.doCall());
+
+ Assertions.assertTrue(printer.getOutput().startsWith("Name: main"),
printer.getOutput());
+ }
+
private CatalogDoc createCommand(String name, boolean example) {
CatalogDoc command = new CatalogDoc(new
CamelJBangMain().withPrinter(printer));
command.name = name;
diff --git
a/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/process/ListErrorTest.java
b/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/process/ListErrorTest.java
index c17661f43cb5..7cee8e177fa3 100644
---
a/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/process/ListErrorTest.java
+++
b/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/process/ListErrorTest.java
@@ -18,116 +18,237 @@ package org.apache.camel.dsl.jbang.core.commands.process;
import java.nio.file.Files;
import java.nio.file.Path;
-import java.util.Collections;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Map;
import java.util.stream.Stream;
import org.apache.camel.dsl.jbang.core.commands.CamelJBangMain;
+import org.apache.camel.dsl.jbang.core.common.CliException;
+import org.apache.camel.dsl.jbang.core.common.CliExitCode;
import org.apache.camel.dsl.jbang.core.common.CommandLineHelper;
import org.apache.camel.util.json.JsonArray;
import org.apache.camel.util.json.JsonObject;
+import org.apache.camel.util.json.Jsoner;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.MockedStatic;
import org.mockito.junit.jupiter.MockitoExtension;
+import picocli.CommandLine;
-import static org.junit.jupiter.api.Assertions.assertEquals;
-import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
import static org.mockito.Mockito.mockStatic;
+/**
+ * camel get error: the errors the integrations recorded, filtered, and with
their details.
+ */
@ExtendWith(MockitoExtension.class)
class ListErrorTest extends ProcessCommandTestSupport {
+ private static final long OTHER_PID = 23456L;
+
@Test
void testEmptyOutputWhenNoErrors() throws Exception {
- JsonObject status = buildContextStatus("myApp", 5);
- writeStatusFile(TEST_PID, status);
+ writeStatusFile(TEST_PID, buildContextStatus("myApp", 5));
// no error file written
- ListError command = new ListError(new
CamelJBangMain().withPrinter(printer));
- command.sort = "pid";
+ assertThat(call(command())).isZero();
- try (MockedStatic<ProcessHandle> mocked =
mockStatic(ProcessHandle.class)) {
- ProcessHandle ph = mockProcessHandle(TEST_PID);
- ProcessHandle currentHandle = mockCurrentHandle();
- mocked.when(ProcessHandle::current).thenReturn(currentHandle);
- mocked.when(ProcessHandle::allProcesses).thenAnswer(inv ->
Stream.of(ph));
+ assertThat(printer.getOutput()).isEmpty();
+ }
- int exit = command.doCall();
+ @Test
+ void testShowsErrorRouteId() throws Exception {
+ writeStatusFile(TEST_PID, buildContextStatus("myApp", 5));
+ writeErrorFile(TEST_PID, errorEntry("myRoute", "abc-123", false, 0));
- assertEquals(0, exit);
- assertEquals("", printer.getOutput().trim());
- }
+ assertThat(call(command())).isZero();
+
+ assertThat(printer.getOutput()).contains("myApp", "myRoute",
"abc-123", "RuntimeException", "Test error");
}
@Test
- void testShowsErrorRouteId() throws Exception {
- JsonObject status = buildContextStatus("myApp", 5);
- writeStatusFile(TEST_PID, status);
- writeErrorFile(errorEntry());
+ void testJsonOutput() throws Exception {
+ writeStatusFile(TEST_PID, buildContextStatus("myApp", 5));
+ writeErrorFile(TEST_PID, errorEntry("myRoute", "abc-123", false, 0));
- ListError command = new ListError(new
CamelJBangMain().withPrinter(printer));
- command.sort = "pid";
+ assertThat(call(command("--json"))).isZero();
- try (MockedStatic<ProcessHandle> mocked =
mockStatic(ProcessHandle.class)) {
- ProcessHandle ph = mockProcessHandle(TEST_PID);
- ProcessHandle currentHandle = mockCurrentHandle();
- mocked.when(ProcessHandle::current).thenReturn(currentHandle);
- mocked.when(ProcessHandle::allProcesses).thenAnswer(inv ->
Stream.of(ph));
+ JsonArray errors = Jsoner.deserialize(printer.getOutput(), (JsonArray)
null);
+ assertThat(errors).hasSize(1);
+ JsonObject error = errors.getMap(0);
+ assertThat(error.getString("routeId")).isEqualTo("myRoute");
+ }
- int exit = command.doCall();
+ @Test
+ void jsonWithoutErrorsIsAnEmptyArray() throws Exception {
+ writeStatusFile(TEST_PID, buildContextStatus("myApp", 5));
- assertEquals(0, exit);
- String output = printer.getOutput();
- assertTrue(output.contains("myRoute"), "Should show routeId");
- }
+ assertThat(call(command("--json"))).isZero();
+
+ assertThat(printer.getOutput()).isEqualTo("[]");
}
@Test
- void testJsonOutput() throws Exception {
- JsonObject status = buildContextStatus("myApp", 5);
- writeStatusFile(TEST_PID, status);
- writeErrorFile(errorEntry());
+ void filterByRoute() throws Exception {
+ writeTwoErrors();
+
+ assertThat(call(command("--route=orders"))).isZero();
+
+
assertThat(printer.getOutput()).contains("ex-1").doesNotContain("ex-2");
+ }
+
+ @Test
+ void filterByHandledInAnyCase() throws Exception {
+ writeTwoErrors();
+
+ assertThat(call(command("--handled=FALSE"))).isZero();
+
+
assertThat(printer.getOutput()).contains("ex-2").doesNotContain("ex-1");
+ }
+ @Test
+ void filterByExchangeAndException() throws Exception {
+ writeTwoErrors();
+
+ assertThat(call(command("--id=ex-1", "--exception=runtime"))).isZero();
+
+
assertThat(printer.getOutput()).contains("ex-1").doesNotContain("ex-2");
+ }
+
+ @Test
+ void agoShowsTheRecentErrors() throws Exception {
+ writeStatusFile(TEST_PID, buildContextStatus("myApp", 5));
+ writeErrorFile(TEST_PID, errorEntry("orders", "ex-1", false, 5),
errorEntry("orders", "ex-2", false, 600));
+
+ assertThat(call(command("--ago=1m"))).isZero();
+
+
assertThat(printer.getOutput()).contains("ex-1").doesNotContain("ex-2");
+ }
+
+ @Test
+ void handledThatIsNotABooleanIsAUsageError() {
+ // before the integrations are read
+ ListError command = command("--handled=yes");
+
+ assertThatThrownBy(command::doCall)
+ .isInstanceOfSatisfying(CliException.class,
+ e ->
assertThat(e.getExitCode()).isEqualTo(CliExitCode.USAGE))
+ .hasMessage("--handled must be true or false, was: yes");
+ }
+
+ @Test
+ void agoThatIsNotADurationIsAUsageError() {
+ ListError command = command("--ago=recently");
+
+ assertThatThrownBy(command::doCall)
+ .isInstanceOfSatisfying(CliException.class,
+ e ->
assertThat(e.getExitCode()).isEqualTo(CliExitCode.USAGE))
+ .hasMessage("--ago must be a duration such as 60s, 5m or 1h,
was: recently");
+ }
+
+ @Test
+ void limitShowsTheFirstErrors() throws Exception {
+ writeStatusFile(TEST_PID, buildContextStatus("myApp", 5));
+ writeErrorFile(TEST_PID, errorEntry("orders", "ex-1", false, 0),
errorEntry("orders", "ex-2", false, 10));
+
+ assertThat(call(command("--limit=1"))).isZero();
+
+
assertThat(printer.getOutput()).contains("ex-1").doesNotContain("ex-2");
+ }
+
+ @Test
+ void lastShowsTheNewestErrorOfAnyIntegration() throws Exception {
+ writeStatusFile(TEST_PID, buildContextStatus("orders", 5));
+ writeStatusFile(OTHER_PID, buildContextStatus("payments", 5));
+ writeErrorFile(TEST_PID, errorEntry("orders", "ex-old", false, 60));
+ writeErrorFile(OTHER_PID, errorEntry("payments", "ex-new", false, 1));
+
+ assertThat(call(command("--last"))).isZero();
+
+ // with the details of the error
+ assertThat(printer.getOutput()).contains("ex-new", "Hello", "History",
"payments[log1]")
+ .doesNotContain("ex-old");
+ }
+
+ @Test
+ void showTheBodyAndHistoryOfTheErrors() throws Exception {
+ writeStatusFile(TEST_PID, buildContextStatus("myApp", 5));
+ writeErrorFile(TEST_PID, errorEntry("orders", "ex-1", false, 0));
+
+ assertThat(call(command("--show=body,history"))).isZero();
+
+ assertThat(printer.getOutput()).contains("Hello", "History",
"orders[log1]");
+ }
+
+ @Test
+ void diagramWithoutMessageHistoryIsAnError() throws Exception {
+ writeStatusFile(TEST_PID, buildContextStatus("myApp", 5));
+ JsonObject error = errorEntry("orders", "ex-1", false, 0);
+ error.remove("messageHistory");
+ writeErrorFile(TEST_PID, error);
+
+ assertThat(call(command("--diagram"))).isEqualTo(CliExitCode.FAILURE);
+
+ assertThat(printer.getStderr()).contains("No message history available
for this error");
+ }
+
+ @Test
+ void unknownSortIsAUsageError() {
+ ListError command = command("--sort=size");
+
+ assertThatThrownBy(command::doCall)
+ .isInstanceOfSatisfying(CliException.class,
+ e ->
assertThat(e.getExitCode()).isEqualTo(CliExitCode.USAGE));
+ }
+
+ private ListError command(String... args) {
ListError command = new ListError(new
CamelJBangMain().withPrinter(printer));
- command.sort = "pid";
- command.jsonOutput = true;
+ CommandLine.populateCommand(command,
Stream.concat(Stream.of("--logging-color=false"), Arrays.stream(args))
+ .toArray(String[]::new));
+ return command;
+ }
+ // runs the command with the integrations of the status files of the test
+ private static int call(ListError command) throws Exception {
try (MockedStatic<ProcessHandle> mocked =
mockStatic(ProcessHandle.class)) {
- ProcessHandle ph = mockProcessHandle(TEST_PID);
+ List<ProcessHandle> handles = Stream.of(TEST_PID, OTHER_PID)
+ .filter(pid ->
Files.exists(CommandLineHelper.getCamelDir().resolve(pid + "-status.json")))
+
.map(ProcessCommandTestSupport::mockProcessHandle).toList();
ProcessHandle currentHandle = mockCurrentHandle();
mocked.when(ProcessHandle::current).thenReturn(currentHandle);
- mocked.when(ProcessHandle::allProcesses).thenAnswer(inv ->
Stream.of(ph));
-
- int exit = command.doCall();
-
- assertEquals(0, exit);
- String output = printer.getOutput();
- assertTrue(output.startsWith("["), "JSON output should be array");
- assertTrue(output.contains("myRoute"));
+ mocked.when(ProcessHandle::allProcesses).thenAnswer(inv ->
handles.stream());
+ return command.doCall();
}
}
- private static void writeErrorFile(JsonObject... errors) throws Exception {
- JsonArray arr = new JsonArray();
- Collections.addAll(arr, errors);
+ private static void writeTwoErrors() throws Exception {
+ writeStatusFile(TEST_PID, buildContextStatus("myApp", 5));
+ // newest first, as the error registry lists them
+ writeErrorFile(TEST_PID, errorEntry("orders", "ex-1", true, 0),
errorEntry("payments", "ex-2", false, 10));
+ }
+
+ private static void writeErrorFile(long pid, JsonObject... errors) throws
Exception {
JsonObject root = new JsonObject();
- root.put("errors", arr);
- Path f =
CommandLineHelper.getCamelDir().resolve(ProcessCommandTestSupport.TEST_PID +
"-error.json");
+ root.put("errors", new JsonArray(Arrays.asList(errors)));
+ Path f = CommandLineHelper.getCamelDir().resolve(pid + "-error.json");
Files.writeString(f, root.toJson());
}
- private static JsonObject errorEntry() {
- JsonObject ex = new JsonObject();
- ex.put("type", "java.lang.RuntimeException");
- ex.put("message", "Test error");
-
+ private static JsonObject errorEntry(String routeId, String exchangeId,
boolean handled, long secondsAgo) {
+ JsonObject body = new JsonObject();
+ body.put("type", "java.lang.String");
+ body.put("value", "Hello");
JsonObject e = new JsonObject();
- e.put("routeId", "myRoute");
- e.put("nodeId", "myNode");
- e.put("exchangeId", "abc-123");
- e.put("handled", false);
- e.put("timestamp", System.currentTimeMillis());
- e.put("exception", ex);
+ e.put("routeId", routeId);
+ e.put("nodeId", "log1");
+ e.put("exchangeId", exchangeId);
+ e.put("handled", handled);
+ e.put("timestamp", System.currentTimeMillis() - secondsAgo * 1000);
+ e.put("exception", new JsonObject(Map.of("type",
"java.lang.RuntimeException", "message", "Test error")));
+ e.put("message", new JsonObject(Map.of("body", body)));
+ e.put("messageHistory", new JsonArray(List.of(routeId + "[from1]",
routeId + "[log1]")));
return e;
}
}
diff --git
a/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/update/UpdateRunTest.java
b/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/update/UpdateRunTest.java
new file mode 100644
index 000000000000..aa1cfc553788
--- /dev/null
+++
b/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/update/UpdateRunTest.java
@@ -0,0 +1,269 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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 org.apache.camel.dsl.jbang.core.commands.update;
+
+import java.io.OutputStream;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.jar.JarOutputStream;
+import java.util.zip.ZipEntry;
+
+import org.apache.camel.dsl.jbang.core.commands.CamelCommandBaseTestSupport;
+import org.apache.camel.dsl.jbang.core.commands.CamelJBangMain;
+import org.apache.camel.dsl.jbang.core.common.CliException;
+import org.apache.camel.dsl.jbang.core.common.CliExitCode;
+import org.apache.camel.main.download.MavenDependencyDownloader;
+import org.apache.camel.tooling.maven.MavenArtifact;
+import org.apache.camel.tooling.maven.MavenGav;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.io.TempDir;
+import picocli.CommandLine;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
+
+/**
+ * camel update run: the Maven command that updates a project with the Camel
upgrade recipes (Camel Main and Spring
+ * Boot) or the Quarkus Maven plugin (Quarkus). The command is not run, and
nothing is downloaded.
+ */
+class UpdateRunTest extends CamelCommandBaseTestSupport {
+
+ @TempDir
+ Path project;
+
+ @TempDir
+ Path repo;
+
+ // what the fake downloader has, by artifact id
+ private final Map<String, Path> artifacts = new HashMap<>();
+ // the versions of the Camel Quarkus catalog: the Camel version and the
Camel Quarkus version
+ private final List<String[]> quarkusVersions = new ArrayList<>();
+ // the command the update ran
+ private List<String> command;
+
+ @BeforeEach
+ void pom() throws Exception {
+ Files.writeString(project.resolve("pom.xml"), "<project/>");
+ }
+
+ @Test
+ void withoutAMavenProjectIsNotFound() throws Exception {
+ Files.delete(project.resolve("pom.xml"));
+
+
assertThat(command("4.18.0").doCall()).isEqualTo(CliExitCode.NOT_FOUND);
+
+ assertThat(printer.getStderr()).contains("No Maven project found in
the current directory");
+ assertThat(command).isNull();
+ }
+
+ @Test
+ void camelMainRunsTheRewritePluginWithTheLatestRecipe() throws Exception {
+ recipes("camel-upgrade-recipes", "4.4.yaml",
"org.apache.camel.upgrade.camel44.CamelMigrationRecipe",
+ "latest.yaml",
"org.apache.camel.upgrade.CamelMigrationRecipe");
+
+ assertThat(command("4.18.0").doCall()).isZero();
+
+ assertThat(command).contains("--no-transfer-progress",
+ "org.openrewrite.maven:rewrite-maven-plugin:6.0.4:run",
+
"-Drewrite.recipeArtifactCoordinates=org.apache.camel.upgrade:camel-upgrade-recipes:4.18.0",
+
"-Drewrite.activeRecipes=org.apache.camel.upgrade.CamelMigrationRecipe");
+ }
+
+ @Test
+ void everyRecipeWithoutALatestOne() throws Exception {
+ recipes("camel-upgrade-recipes", "4.4.yaml",
"org.apache.camel.upgrade.camel44.CamelMigrationRecipe");
+
+ assertThat(command("4.4.0").doCall()).isZero();
+
+
assertThat(command).contains("-Drewrite.activeRecipes=org.apache.camel.upgrade.camel44.CamelMigrationRecipe");
+ }
+
+ @Test
+ void dryRunWithDebugAndExtraRecipes() throws Exception {
+ recipes("camel-upgrade-recipes", "latest.yaml",
"org.apache.camel.upgrade.CamelMigrationRecipe");
+
+ assertThat(command("4.18.0", "--dryRun", "--debug",
"--openRewriteVersion=6.1.0",
+ "--extraActiveRecipes=org.example.MyRecipe",
"--extraRecipeArtifactCoordinates=org.example:recipes:1.0")
+ .doCall()).isZero();
+
+ assertThat(command).contains("-X",
"org.openrewrite.maven:rewrite-maven-plugin:6.1.0:dryRun",
+
"-Drewrite.recipeArtifactCoordinates=org.apache.camel.upgrade:camel-upgrade-recipes:4.18.0,"
+
+ "org.example:recipes:1.0",
+
"-Drewrite.activeRecipes=org.apache.camel.upgrade.CamelMigrationRecipe,org.example.MyRecipe");
+ }
+
+ @Test
+ void springBootUsesItsRecipes() throws Exception {
+ recipes("camel-spring-boot-upgrade-recipes", "latest.yaml",
+ "org.apache.camel.upgrade.CamelSpringBootMigrationRecipe");
+
+ assertThat(command("4.18.0",
"--runtime=spring-boot").doCall()).isZero();
+
+ assertThat(command).contains(
+
"-Drewrite.recipeArtifactCoordinates=org.apache.camel.upgrade:camel-spring-boot-upgrade-recipes:4.18.0",
+
"-Drewrite.activeRecipes=org.apache.camel.upgrade.CamelSpringBootMigrationRecipe");
+ }
+
+ @Test
+ void versionWithoutRecipesIsAFailure() throws Exception {
+ assertThat(command("9.9.9").doCall()).isEqualTo(CliExitCode.FAILURE);
+
+ assertThat(printer.getStderr())
+ .contains("Cannot find the Camel upgrade recipes
org.apache.camel.upgrade:camel-upgrade-recipes:9.9.9");
+ assertThat(command).isNull();
+ }
+
+ @Test
+ void quarkusRunsTheQuarkusUpdateOfTheStreamOfTheCamelVersion() throws
Exception {
+ quarkusVersions.add(new String[] { "4.14.0", "3.27.1" });
+ quarkusVersions.add(new String[] { "4.18.0", "3.33.2" });
+
+ assertThat(command("4.18.0", "--runtime=quarkus",
"--quarkusMavenPluginVersion=3.33.2").doCall()).isZero();
+
+
assertThat(command).contains("io.quarkus.platform:quarkus-maven-plugin:3.33.2:update",
"-Dstream=3.33",
+ "-DrewriteFullRun");
+ }
+
+ @Test
+ void quarkusDryRun() throws Exception {
+ quarkusVersions.add(new String[] { "4.18.0", "3.33.2" });
+
+ assertThat(command("4.18.0", "--runtime=quarkus",
"--quarkusMavenPluginVersion=3.33.2", "--dryRun").doCall())
+ .isZero();
+
+ assertThat(command).contains("-DrewriteDryRun");
+ }
+
+ @Test
+ void quarkusWithoutTheCamelVersionIsAFailure() throws Exception {
+ quarkusVersions.add(new String[] { "4.14.0", "3.27.1" });
+
+ assertThat(command("4.18.0", "--runtime=quarkus",
"--quarkusMavenPluginVersion=3.33.2").doCall())
+ .isEqualTo(CliExitCode.FAILURE);
+
+ assertThat(printer.getStderr()).contains("Cannot determine Quarkus
stream version for Camel version 4.18.0");
+ }
+
+ @Test
+ void quarkusWithoutDownloadNeedsThePluginVersion() {
+ UpdateRun command = command("4.18.0", "--runtime=quarkus",
"--download=false");
+
+ assertThatThrownBy(command::doCall)
+ .isInstanceOfSatisfying(CliException.class,
+ e ->
assertThat(e.getExitCode()).isEqualTo(CliExitCode.USAGE))
+ .hasMessageContaining("--quarkusMavenPluginVersion is required
with --download=false");
+ }
+
+ @Test
+ void theExitCodeOfMaven() throws Exception {
+ recipes("camel-upgrade-recipes", "latest.yaml",
"org.apache.camel.upgrade.CamelMigrationRecipe");
+ UpdateRun command = new UpdateRun(new
CamelJBangMain().withPrinter(printer)) {
+ @Override
+ protected MavenDependencyDownloader createDownloader() {
+ return downloader();
+ }
+
+ @Override
+ protected int executeCommand(List<String> command) {
+ return 1;
+ }
+ };
+ CommandLine.populateCommand(command, "4.18.0");
+ command.projectDir = project;
+
+ assertThat(command.doCall()).isEqualTo(1);
+ }
+
+ private UpdateRun command(String... args) {
+ UpdateRun command = new UpdateRun(new
CamelJBangMain().withPrinter(printer)) {
+ @Override
+ protected MavenDependencyDownloader createDownloader() {
+ return downloader();
+ }
+
+ @Override
+ protected int executeCommand(List<String> command) {
+ UpdateRunTest.this.command = command;
+ return 0;
+ }
+ };
+ CommandLine.populateCommand(command, args);
+ command.projectDir = project;
+ return command;
+ }
+
+ // a downloader with the recipes and versions of the test, and nothing else
+ private MavenDependencyDownloader downloader() {
+ return new MavenDependencyDownloader() {
+ @Override
+ public MavenArtifact downloadArtifact(String groupId, String
artifactId, String version) {
+ Path jar = artifacts.get(artifactId);
+ return jar != null
+ ? new MavenArtifact(
+ MavenGav.parseGav(groupId + ":" + artifactId +
":" + version),
+ jar.toFile())
+ : null;
+ }
+
+ @Override
+ public List<String[]> resolveAvailableVersions(
+ String groupId, String artifactId, String minimumVersion,
String repo) {
+ return quarkusVersions;
+ }
+
+ @Override
+ protected void doBuild() {
+ }
+
+ @Override
+ protected void doInit() {
+ }
+
+ @Override
+ protected void doStart() {
+ }
+
+ @Override
+ protected void doStop() {
+ }
+ };
+ }
+
+ // a jar of recipes: pairs of the file name and the name of the recipe in
it
+ private void recipes(String artifactId, String... files) throws Exception {
+ Path jar = repo.resolve(artifactId + ".jar");
+ try (JarOutputStream out = new
JarOutputStream(Files.newOutputStream(jar))) {
+ for (int i = 0; i < files.length; i += 2) {
+ out.putNextEntry(new ZipEntry("META-INF/rewrite/" + files[i]));
+ write(out, "---\ntype:
specs.openrewrite.org/v1beta/recipe\nname: " + files[i + 1]
+ + "\ndisplayName: Migrates Camel\n");
+ out.closeEntry();
+ }
+ }
+ artifacts.put(artifactId, jar);
+ }
+
+ private static void write(OutputStream out, String text) throws Exception {
+ out.write(text.getBytes(StandardCharsets.UTF_8));
+ }
+}