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 064fff3be18e CAMEL-25544: camel-jira - The producers wait for the
result of their request (#27692)
064fff3be18e is described below
commit 064fff3be18e455af348f73b724e0c463611c760
Author: allthingssecurity <[email protected]>
AuthorDate: Tue Oct 13 01:47:10 2026 +0530
CAMEL-25544: camel-jira - The producers wait for the result of their
request (#27692)
Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
Co-Authored-By: Claude Opus 5.5 <[email protected]>
---
.../jira/producer/AddCommentProducer.java | 3 +-
.../jira/producer/AddIssueLinkProducer.java | 3 +-
.../component/jira/producer/AddIssueProducer.java | 9 +-
.../jira/producer/AddWorkLogProducer.java | 4 +-
.../jira/producer/AttachFileProducer.java | 6 +-
.../jira/producer/DeleteIssueProducer.java | 3 +-
.../jira/producer/TransitionIssueProducer.java | 6 +-
.../component/jira/producer/WatcherProducer.java | 6 +-
.../jira/producer/AddCommentProducerTest.java | 5 +-
.../jira/producer/AddIssueLinkProducerTest.java | 10 +-
.../jira/producer/AddWorkLogProducerTest.java | 5 +-
.../AttachFileProducerFromFileRouteTest.java | 2 +-
.../AttachFileProducerInputStreamTest.java | 2 +-
.../jira/producer/AttachFileProducerTest.java | 2 +-
.../producer/DeleteIssueProducerFailureTest.java | 101 +++++++++
.../jira/producer/DeleteIssueProducerTest.java | 2 +-
.../producer/JiraProducerRejectedRequestTest.java | 235 +++++++++++++++++++++
.../jira/producer/TransitionIssueProducerTest.java | 2 +-
.../jira/producer/WatcherProducerTest.java | 4 +-
19 files changed, 380 insertions(+), 30 deletions(-)
diff --git
a/components/camel-jira/src/main/java/org/apache/camel/component/jira/producer/AddCommentProducer.java
b/components/camel-jira/src/main/java/org/apache/camel/component/jira/producer/AddCommentProducer.java
index a1fe4080fb9f..fa27671043c1 100644
---
a/components/camel-jira/src/main/java/org/apache/camel/component/jira/producer/AddCommentProducer.java
+++
b/components/camel-jira/src/main/java/org/apache/camel/component/jira/producer/AddCommentProducer.java
@@ -54,6 +54,7 @@ public class AddCommentProducer extends DefaultProducer {
// there is a bug in addComment, it doesn't use the author parameter
to add the comment
// it uses the authenticated username.
https://ecosystem.atlassian.net/browse/JRJC-241
Comment comment = new Comment(issue.getSelf(), commentStr, null, null,
now, null, null, null);
- issueClient.addComment(commentsUri, comment);
+ // wait for the result, so that a request that Jira rejects fails the
exchange
+ issueClient.addComment(commentsUri, comment).claim();
}
}
diff --git
a/components/camel-jira/src/main/java/org/apache/camel/component/jira/producer/AddIssueLinkProducer.java
b/components/camel-jira/src/main/java/org/apache/camel/component/jira/producer/AddIssueLinkProducer.java
index cd27531b4235..152dc230f2c1 100644
---
a/components/camel-jira/src/main/java/org/apache/camel/component/jira/producer/AddIssueLinkProducer.java
+++
b/components/camel-jira/src/main/java/org/apache/camel/component/jira/producer/AddIssueLinkProducer.java
@@ -60,6 +60,7 @@ public class AddIssueLinkProducer extends DefaultProducer {
}
LinkIssuesInput linkIssuesInput = new LinkIssuesInput(parentIssueKey,
childIssueKey, linkType, comment);
- issueClient.linkIssue(linkIssuesInput);
+ // wait for the result, so that a request that Jira rejects fails the
exchange
+ issueClient.linkIssue(linkIssuesInput).claim();
}
}
diff --git
a/components/camel-jira/src/main/java/org/apache/camel/component/jira/producer/AddIssueProducer.java
b/components/camel-jira/src/main/java/org/apache/camel/component/jira/producer/AddIssueProducer.java
index 9ccd3c9a393f..6f01eb46a5cf 100644
---
a/components/camel-jira/src/main/java/org/apache/camel/component/jira/producer/AddIssueProducer.java
+++
b/components/camel-jira/src/main/java/org/apache/camel/component/jira/producer/AddIssueProducer.java
@@ -117,18 +117,19 @@ public class AddIssueProducer extends DefaultProducer {
IssueRestClient issueClient = client.getIssueClient();
BasicIssue issueCreated =
issueClient.createIssue(builder.build()).claim();
Issue issue = issueClient.getIssue(issueCreated.getKey()).claim();
+ // support InOut. The created issue is set before the watchers are
added, so that it is on the exchange when
+ // adding a watcher fails
+ ExchangeHelper.setInOutBodyPatternAware(exchange, issue);
if (ObjectHelper.isNotEmpty(watchers)) {
String[] watArr = watchers.split(",");
for (String s : watArr) {
String watcher = s.trim();
if (!watcher.isEmpty()) {
- issueClient.addWatcher(issue.getWatchers().getSelf(),
watcher);
+ // wait for the result, so that a request that Jira
rejects fails the exchange
+ issueClient.addWatcher(issue.getWatchers().getSelf(),
watcher).claim();
}
}
}
-
- // support InOut
- ExchangeHelper.setInOutBodyPatternAware(exchange, issue);
}
}
diff --git
a/components/camel-jira/src/main/java/org/apache/camel/component/jira/producer/AddWorkLogProducer.java
b/components/camel-jira/src/main/java/org/apache/camel/component/jira/producer/AddWorkLogProducer.java
index ce687f99ebe3..d501a9957b5d 100644
---
a/components/camel-jira/src/main/java/org/apache/camel/component/jira/producer/AddWorkLogProducer.java
+++
b/components/camel-jira/src/main/java/org/apache/camel/component/jira/producer/AddWorkLogProducer.java
@@ -57,7 +57,7 @@ public class AddWorkLogProducer extends DefaultProducer {
WorklogInput worklogInput = WorklogInput.create(issue.getSelf(),
comment, new DateTime(), minutesSpent);
- issueClient.addWorklog(issue.getWorklogUri(), worklogInput);
-
+ // wait for the result, so that a request that Jira rejects fails the
exchange
+ issueClient.addWorklog(issue.getWorklogUri(), worklogInput).claim();
}
}
diff --git
a/components/camel-jira/src/main/java/org/apache/camel/component/jira/producer/AttachFileProducer.java
b/components/camel-jira/src/main/java/org/apache/camel/component/jira/producer/AttachFileProducer.java
index 1bcd2cae4396..7f9d88d208e3 100644
---
a/components/camel-jira/src/main/java/org/apache/camel/component/jira/producer/AttachFileProducer.java
+++
b/components/camel-jira/src/main/java/org/apache/camel/component/jira/producer/AttachFileProducer.java
@@ -66,10 +66,12 @@ public class AttachFileProducer extends DefaultProducer {
IssueRestClient issueClient = client.getIssueClient();
Issue issue = issueClient.getIssue(issueKey).claim();
URI attachmentsUri = issue.getAttachmentsUri();
+ // wait for the result, so that the body is read before the exchange
continues (it may be closed or deleted
+ // when the exchange is done), and a request that Jira rejects fails
the exchange
if (file != null) {
- issueClient.addAttachments(attachmentsUri, file);
+ issueClient.addAttachments(attachmentsUri, file).claim();
} else {
- issueClient.addAttachment(attachmentsUri, is, name);
+ issueClient.addAttachment(attachmentsUri, is, name).claim();
}
}
}
diff --git
a/components/camel-jira/src/main/java/org/apache/camel/component/jira/producer/DeleteIssueProducer.java
b/components/camel-jira/src/main/java/org/apache/camel/component/jira/producer/DeleteIssueProducer.java
index a0892a06d7e2..f23e3a92f8b7 100644
---
a/components/camel-jira/src/main/java/org/apache/camel/component/jira/producer/DeleteIssueProducer.java
+++
b/components/camel-jira/src/main/java/org/apache/camel/component/jira/producer/DeleteIssueProducer.java
@@ -39,6 +39,7 @@ public class DeleteIssueProducer extends DefaultProducer {
}
JiraRestClient client = ((JiraEndpoint) getEndpoint()).getClient();
IssueRestClient issueClient = client.getIssueClient();
- issueClient.deleteIssue(issueKey, true);
+ // wait for the result, so that a request that Jira rejects fails the
exchange
+ issueClient.deleteIssue(issueKey, true).claim();
}
}
diff --git
a/components/camel-jira/src/main/java/org/apache/camel/component/jira/producer/TransitionIssueProducer.java
b/components/camel-jira/src/main/java/org/apache/camel/component/jira/producer/TransitionIssueProducer.java
index 91a0d77af164..d041a40823d6 100644
---
a/components/camel-jira/src/main/java/org/apache/camel/component/jira/producer/TransitionIssueProducer.java
+++
b/components/camel-jira/src/main/java/org/apache/camel/component/jira/producer/TransitionIssueProducer.java
@@ -54,13 +54,15 @@ public class TransitionIssueProducer extends
DefaultProducer {
IssueRestClient issueClient = client.getIssueClient();
Issue issue = issueClient.getIssue(issueKey).claim();
TransitionInput transitionInput = new TransitionInput(transitionId);
- issueClient.transition(issue, transitionInput);
+ // wait for the results, so that the comment is added after the
transition, and a request that Jira rejects
+ // fails the exchange
+ issueClient.transition(issue, transitionInput).claim();
// add a comment in an additional operation as using the
TransitionInput doesn't add the comment.
if (commentStr != null) {
URI commentsUri = issue.getCommentsUri();
// there is a bug in addComment, it doesn't use the author
parameter to add the comment
// it uses the authenticated username.
https://ecosystem.atlassian.net/browse/JRJC-241
- issueClient.addComment(commentsUri, Comment.valueOf(commentStr));
+ issueClient.addComment(commentsUri,
Comment.valueOf(commentStr)).claim();
}
}
}
diff --git
a/components/camel-jira/src/main/java/org/apache/camel/component/jira/producer/WatcherProducer.java
b/components/camel-jira/src/main/java/org/apache/camel/component/jira/producer/WatcherProducer.java
index f9b869270ad9..2b5f75264615 100644
---
a/components/camel-jira/src/main/java/org/apache/camel/component/jira/producer/WatcherProducer.java
+++
b/components/camel-jira/src/main/java/org/apache/camel/component/jira/producer/WatcherProducer.java
@@ -54,7 +54,8 @@ public class WatcherProducer extends DefaultProducer {
for (String s : watArr) {
String watcher = s.trim();
if (!watcher.isEmpty()) {
- issueClient.addWatcher(issue.getWatchers().getSelf(),
watcher);
+ // wait for the result, so that a request that Jira
rejects fails the exchange
+ issueClient.addWatcher(issue.getWatchers().getSelf(),
watcher).claim();
}
}
}
@@ -63,7 +64,8 @@ public class WatcherProducer extends DefaultProducer {
for (String s : watArr) {
String watcher = s.trim();
if (!watcher.isEmpty()) {
-
issueClient.removeWatcher(issue.getWatchers().getSelf(), watcher);
+ // wait for the result, so that a request that Jira
rejects fails the exchange
+
issueClient.removeWatcher(issue.getWatchers().getSelf(), watcher).claim();
}
}
}
diff --git
a/components/camel-jira/src/test/java/org/apache/camel/component/jira/producer/AddCommentProducerTest.java
b/components/camel-jira/src/test/java/org/apache/camel/component/jira/producer/AddCommentProducerTest.java
index 79e8c95e6d8c..0b06e98fdd16 100644
---
a/components/camel-jira/src/test/java/org/apache/camel/component/jira/producer/AddCommentProducerTest.java
+++
b/components/camel-jira/src/test/java/org/apache/camel/component/jira/producer/AddCommentProducerTest.java
@@ -25,6 +25,7 @@ import com.atlassian.jira.rest.client.api.JiraRestClient;
import com.atlassian.jira.rest.client.api.JiraRestClientFactory;
import com.atlassian.jira.rest.client.api.domain.Comment;
import com.atlassian.jira.rest.client.api.domain.Issue;
+import io.atlassian.util.concurrent.Promise;
import io.atlassian.util.concurrent.Promises;
import org.apache.camel.CamelContext;
import org.apache.camel.EndpointInject;
@@ -81,14 +82,14 @@ public class AddCommentProducerTest extends
CamelTestSupport {
when(jiraRestClientFactory.createWithBasicHttpAuthentication(any(),
any(), any())).thenReturn(jiraClient);
when(jiraClient.getIssueClient()).thenReturn(issueRestClient);
- when(issueRestClient.addComment(any(), any())).then((Answer<Void>) inv
-> {
+ when(issueRestClient.addComment(any(),
any())).then((Answer<Promise<Void>>) inv -> {
Collection<Comment> comments = new ArrayList<>();
for (Comment c : backendIssue.getComments()) {
comments.add(c);
}
comments.add(newComment(backendIssue.getId(), comments.size() + 1,
comment));
backendIssue = createIssueWithComments(backendIssue.getId(),
comments);
- return null;
+ return Promises.promise(null);
});
backendIssue = createIssueWithComments(1, 1);
when(issueRestClient.getIssue(any())).then(inv ->
Promises.promise(backendIssue));
diff --git
a/components/camel-jira/src/test/java/org/apache/camel/component/jira/producer/AddIssueLinkProducerTest.java
b/components/camel-jira/src/test/java/org/apache/camel/component/jira/producer/AddIssueLinkProducerTest.java
index 2744e630104e..1b41165d6239 100644
---
a/components/camel-jira/src/test/java/org/apache/camel/component/jira/producer/AddIssueLinkProducerTest.java
+++
b/components/camel-jira/src/test/java/org/apache/camel/component/jira/producer/AddIssueLinkProducerTest.java
@@ -28,6 +28,8 @@ import
com.atlassian.jira.rest.client.api.JiraRestClientFactory;
import com.atlassian.jira.rest.client.api.domain.Issue;
import com.atlassian.jira.rest.client.api.domain.IssueLink;
import com.atlassian.jira.rest.client.api.domain.input.LinkIssuesInput;
+import io.atlassian.util.concurrent.Promise;
+import io.atlassian.util.concurrent.Promises;
import org.apache.camel.CamelContext;
import org.apache.camel.CamelExecutionException;
import org.apache.camel.EndpointInject;
@@ -128,11 +130,11 @@ public class AddIssueLinkProducerTest extends
CamelTestSupport {
headers.put(LINK_TYPE, linkType);
when(issueRestClient.linkIssue(any(LinkIssuesInput.class)))
- .then((Answer<Void>) inv -> {
+ .then((Answer<Promise<Void>>) inv -> {
Collection<IssueLink> links = new ArrayList<>();
links.add(newIssueLink(childIssue.getId(), 1, comment));
parentIssue = createIssueWithLinks(parentIssue.getId(),
links);
- return null;
+ return Promises.promise(null);
});
template.sendBodyAndHeaders(comment, headers);
@@ -152,11 +154,11 @@ public class AddIssueLinkProducerTest extends
CamelTestSupport {
headers.put(LINK_TYPE, linkType);
when(issueRestClient.linkIssue(any(LinkIssuesInput.class)))
- .then((Answer<Void>) inv -> {
+ .then((Answer<Promise<Void>>) inv -> {
Collection<IssueLink> links = new ArrayList<>();
links.add(newIssueLink(childIssue.getId(), 1, null));
parentIssue = createIssueWithLinks(parentIssue.getId(),
links);
- return null;
+ return Promises.promise(null);
});
template.sendBodyAndHeaders(null, headers);
diff --git
a/components/camel-jira/src/test/java/org/apache/camel/component/jira/producer/AddWorkLogProducerTest.java
b/components/camel-jira/src/test/java/org/apache/camel/component/jira/producer/AddWorkLogProducerTest.java
index 0cf40ecaf099..ace025c49f9f 100644
---
a/components/camel-jira/src/test/java/org/apache/camel/component/jira/producer/AddWorkLogProducerTest.java
+++
b/components/camel-jira/src/test/java/org/apache/camel/component/jira/producer/AddWorkLogProducerTest.java
@@ -29,6 +29,7 @@ import
com.atlassian.jira.rest.client.api.JiraRestClientFactory;
import com.atlassian.jira.rest.client.api.domain.Issue;
import com.atlassian.jira.rest.client.api.domain.Worklog;
import com.atlassian.jira.rest.client.api.domain.input.WorklogInput;
+import io.atlassian.util.concurrent.Promise;
import io.atlassian.util.concurrent.Promises;
import org.apache.camel.CamelContext;
import org.apache.camel.CamelExecutionException;
@@ -127,11 +128,11 @@ public class AddWorkLogProducerTest extends
CamelTestSupport {
String comment = "A new test comment " + new Date();
when(issueRestClient.addWorklog(any(URI.class),
any(WorklogInput.class)))
- .then((Answer<Void>) inv -> {
+ .then((Answer<Promise<Void>>) inv -> {
Collection<Worklog> workLogs = new ArrayList<>();
workLogs.add(newWorkLog(backendIssue.getId(),
minutesSpent, comment));
backendIssue =
createIssueWithWorkLogs(backendIssue.getId(), workLogs);
- return null;
+ return Promises.promise(null);
});
template.sendBodyAndHeaders(comment, headers);
diff --git
a/components/camel-jira/src/test/java/org/apache/camel/component/jira/producer/AttachFileProducerFromFileRouteTest.java
b/components/camel-jira/src/test/java/org/apache/camel/component/jira/producer/AttachFileProducerFromFileRouteTest.java
index b997b5a1d73a..81184f21f02c 100644
---
a/components/camel-jira/src/test/java/org/apache/camel/component/jira/producer/AttachFileProducerFromFileRouteTest.java
+++
b/components/camel-jira/src/test/java/org/apache/camel/component/jira/producer/AttachFileProducerFromFileRouteTest.java
@@ -106,7 +106,7 @@ public class AttachFileProducerFromFileRouteTest extends
CamelTestSupport {
// re-create the issue with the attachment sent by the route
issue = createIssueWithAttachment(issue.getId(),
issue.getSummary(), issue.getKey(), issue.getIssueType(),
issue.getDescription(), issue.getPriority(),
issue.getAssignee(), attachments);
- return null;
+ return Promises.promise(null);
});
}
diff --git
a/components/camel-jira/src/test/java/org/apache/camel/component/jira/producer/AttachFileProducerInputStreamTest.java
b/components/camel-jira/src/test/java/org/apache/camel/component/jira/producer/AttachFileProducerInputStreamTest.java
index 6acc2cfdeb1b..714214e98be0 100644
---
a/components/camel-jira/src/test/java/org/apache/camel/component/jira/producer/AttachFileProducerInputStreamTest.java
+++
b/components/camel-jira/src/test/java/org/apache/camel/component/jira/producer/AttachFileProducerInputStreamTest.java
@@ -102,7 +102,7 @@ public class AttachFileProducerInputStreamTest extends
CamelTestSupport {
// re-create the issue with the attachment sent by the route
issue = createIssueWithAttachment(issue.getId(),
issue.getSummary(), issue.getKey(), issue.getIssueType(),
issue.getDescription(), issue.getPriority(),
issue.getAssignee(), attachments);
- return null;
+ return Promises.promise(null);
});
}
diff --git
a/components/camel-jira/src/test/java/org/apache/camel/component/jira/producer/AttachFileProducerTest.java
b/components/camel-jira/src/test/java/org/apache/camel/component/jira/producer/AttachFileProducerTest.java
index 00357bf54a92..5f267a31a68e 100644
---
a/components/camel-jira/src/test/java/org/apache/camel/component/jira/producer/AttachFileProducerTest.java
+++
b/components/camel-jira/src/test/java/org/apache/camel/component/jira/producer/AttachFileProducerTest.java
@@ -105,7 +105,7 @@ public class AttachFileProducerTest extends
CamelTestSupport {
// re-create the issue with the attachment sent by the route
issue = createIssueWithAttachment(issue.getId(),
issue.getSummary(), issue.getKey(), issue.getIssueType(),
issue.getDescription(), issue.getPriority(),
issue.getAssignee(), attachments);
- return null;
+ return Promises.promise(null);
});
}
diff --git
a/components/camel-jira/src/test/java/org/apache/camel/component/jira/producer/DeleteIssueProducerFailureTest.java
b/components/camel-jira/src/test/java/org/apache/camel/component/jira/producer/DeleteIssueProducerFailureTest.java
new file mode 100644
index 000000000000..9ee76c545d76
--- /dev/null
+++
b/components/camel-jira/src/test/java/org/apache/camel/component/jira/producer/DeleteIssueProducerFailureTest.java
@@ -0,0 +1,101 @@
+/*
+ * 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.component.jira.producer;
+
+import java.net.InetSocketAddress;
+import java.nio.charset.StandardCharsets;
+import java.util.List;
+import java.util.concurrent.CopyOnWriteArrayList;
+
+import com.atlassian.jira.rest.client.api.RestClientException;
+import com.sun.net.httpserver.HttpServer;
+import org.apache.camel.Exchange;
+import org.apache.camel.builder.RouteBuilder;
+import org.apache.camel.test.junit6.CamelTestSupport;
+import org.junit.jupiter.api.Test;
+
+import static org.apache.camel.component.jira.JiraConstants.ISSUE_KEY;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertInstanceOf;
+import static org.junit.jupiter.api.Assertions.assertNull;
+
+/**
+ * Uses the real Jira REST client against a local HTTP server: a request that
Jira rejects must fail the exchange.
+ */
+class DeleteIssueProducerFailureTest extends CamelTestSupport {
+
+ private HttpServer jira;
+ private volatile int status;
+ private final List<String> requests = new CopyOnWriteArrayList<>();
+
+ @Override
+ protected void setupResources() throws Exception {
+ jira = HttpServer.create(new InetSocketAddress("localhost", 0), 0);
+ jira.createContext("/", exchange -> {
+ requests.add(exchange.getRequestMethod() + " " +
exchange.getRequestURI().getPath());
+ byte[] body = status == 204
+ ? new byte[0]
+ : "{\"errorMessages\":[\"You do not have permission to
delete issues in this project.\"],\"errors\":{}}"
+ .getBytes(StandardCharsets.UTF_8);
+ exchange.getResponseHeaders().add("Content-Type",
"application/json");
+ exchange.sendResponseHeaders(status, body.length == 0 ? -1 :
body.length);
+ if (body.length > 0) {
+ exchange.getResponseBody().write(body);
+ }
+ exchange.close();
+ });
+ jira.start();
+ }
+
+ @Override
+ protected void cleanupResources() {
+ jira.stop(0);
+ }
+
+ @Test
+ void failsTheExchangeWhenJiraRejectsTheRequest() {
+ status = 403;
+
+ Exchange exchange = template.request("direct:start", e ->
e.getIn().setHeader(ISSUE_KEY, "TST-1"));
+
+ assertEquals(List.of("DELETE /rest/api/latest/issue/TST-1"), requests);
+ RestClientException e = assertInstanceOf(RestClientException.class,
exchange.getException());
+ assertEquals(403, e.getStatusCode().get());
+ }
+
+ @Test
+ void completesTheExchangeWhenJiraDeletedTheIssue() {
+ status = 204;
+
+ Exchange exchange = template.request("direct:start", e ->
e.getIn().setHeader(ISSUE_KEY, "TST-1"));
+
+ assertEquals(List.of("DELETE /rest/api/latest/issue/TST-1"), requests);
+ assertNull(exchange.getException());
+ }
+
+ @Override
+ protected RouteBuilder createRouteBuilder() {
+ return new RouteBuilder() {
+ @Override
+ public void configure() {
+ from("direct:start")
+ .to("jira:deleteIssue?jiraUrl=http://localhost:" +
jira.getAddress().getPort()
+ + "&username=user&password=secret");
+ }
+ };
+ }
+}
diff --git
a/components/camel-jira/src/test/java/org/apache/camel/component/jira/producer/DeleteIssueProducerTest.java
b/components/camel-jira/src/test/java/org/apache/camel/component/jira/producer/DeleteIssueProducerTest.java
index c4b5c3688322..7ac3d20f4aa0 100644
---
a/components/camel-jira/src/test/java/org/apache/camel/component/jira/producer/DeleteIssueProducerTest.java
+++
b/components/camel-jira/src/test/java/org/apache/camel/component/jira/producer/DeleteIssueProducerTest.java
@@ -81,7 +81,7 @@ public class DeleteIssueProducerTest extends CamelTestSupport
{
when(issueRestClient.getIssue(any())).then(inv ->
Promises.promise(issue));
when(issueRestClient.deleteIssue(anyString(), anyBoolean())).then(inv
-> {
issue = null;
- return null;
+ return Promises.promise(null);
});
}
diff --git
a/components/camel-jira/src/test/java/org/apache/camel/component/jira/producer/JiraProducerRejectedRequestTest.java
b/components/camel-jira/src/test/java/org/apache/camel/component/jira/producer/JiraProducerRejectedRequestTest.java
new file mode 100644
index 000000000000..05069bf73e87
--- /dev/null
+++
b/components/camel-jira/src/test/java/org/apache/camel/component/jira/producer/JiraProducerRejectedRequestTest.java
@@ -0,0 +1,235 @@
+/*
+ * 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.component.jira.producer;
+
+import java.io.ByteArrayInputStream;
+import java.io.File;
+import java.io.InputStream;
+import java.net.URI;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.HashMap;
+import java.util.Map;
+
+import com.atlassian.jira.rest.client.api.IssueRestClient;
+import com.atlassian.jira.rest.client.api.JiraRestClient;
+import com.atlassian.jira.rest.client.api.JiraRestClientFactory;
+import com.atlassian.jira.rest.client.api.RestClientException;
+import com.atlassian.jira.rest.client.api.domain.BasicIssue;
+import com.atlassian.jira.rest.client.api.domain.BasicWatchers;
+import com.atlassian.jira.rest.client.api.domain.Comment;
+import com.atlassian.jira.rest.client.api.domain.Issue;
+import com.atlassian.jira.rest.client.api.domain.input.IssueInput;
+import com.atlassian.jira.rest.client.api.domain.input.LinkIssuesInput;
+import com.atlassian.jira.rest.client.api.domain.input.TransitionInput;
+import com.atlassian.jira.rest.client.api.domain.input.WorklogInput;
+import io.atlassian.util.concurrent.Promise;
+import io.atlassian.util.concurrent.Promises;
+import org.apache.camel.CamelContext;
+import org.apache.camel.Exchange;
+import org.apache.camel.builder.RouteBuilder;
+import org.apache.camel.component.jira.JiraComponent;
+import org.apache.camel.spi.Registry;
+import org.apache.camel.test.junit6.CamelTestSupport;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.junit.jupiter.api.io.TempDir;
+import org.mockito.InOrder;
+import org.mockito.Mock;
+import org.mockito.junit.jupiter.MockitoExtension;
+
+import static org.apache.camel.component.jira.JiraConstants.CHILD_ISSUE_KEY;
+import static org.apache.camel.component.jira.JiraConstants.ISSUE_KEY;
+import static org.apache.camel.component.jira.JiraConstants.ISSUE_PROJECT_KEY;
+import static org.apache.camel.component.jira.JiraConstants.ISSUE_SUMMARY;
+import static
org.apache.camel.component.jira.JiraConstants.ISSUE_TRANSITION_ID;
+import static org.apache.camel.component.jira.JiraConstants.ISSUE_TYPE_ID;
+import static org.apache.camel.component.jira.JiraConstants.ISSUE_WATCHERS_ADD;
+import static
org.apache.camel.component.jira.JiraConstants.ISSUE_WATCHERS_REMOVE;
+import static org.apache.camel.component.jira.JiraConstants.JIRA;
+import static
org.apache.camel.component.jira.JiraConstants.JIRA_REST_CLIENT_FACTORY;
+import static org.apache.camel.component.jira.JiraConstants.LINK_TYPE;
+import static org.apache.camel.component.jira.JiraConstants.MINUTES_SPENT;
+import static org.apache.camel.component.jira.JiraConstants.PARENT_ISSUE_KEY;
+import static
org.apache.camel.component.jira.JiraTestConstants.JIRA_CREDENTIALS;
+import static org.apache.camel.component.jira.JiraTestConstants.KEY;
+import static org.apache.camel.component.jira.JiraTestConstants.TEST_JIRA_URL;
+import static org.apache.camel.component.jira.Utils.createIssue;
+import static org.junit.jupiter.api.Assertions.assertSame;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.ArgumentMatchers.same;
+import static org.mockito.Mockito.inOrder;
+import static org.mockito.Mockito.lenient;
+import static org.mockito.Mockito.when;
+
+/**
+ * The Jira client is asynchronous: a request that Jira rejects (no
permission, an invalid transition, a missing field)
+ * fails its promise. The producers must wait for the promise, so that the
exchange fails too.
+ */
+@ExtendWith(MockitoExtension.class)
+class JiraProducerRejectedRequestTest extends CamelTestSupport {
+
+ private static final String ISSUE = KEY + "-11";
+
+ private final RestClientException rejection
+ = new RestClientException("Jira rejected the request", new
IllegalStateException("HTTP 400"));
+
+ private Issue issue;
+
+ @Mock
+ private JiraRestClient jiraClient;
+ @Mock
+ private JiraRestClientFactory jiraRestClientFactory;
+ @Mock
+ private IssueRestClient issueRestClient;
+
+ @Override
+ protected void bindToRegistry(Registry registry) {
+ registry.bind(JIRA_REST_CLIENT_FACTORY, jiraRestClientFactory);
+ }
+
+ @Override
+ protected CamelContext createCamelContext() throws Exception {
+ URI watchersUri = URI.create(TEST_JIRA_URL + "/rest/api/2/issue/" +
ISSUE + "/watchers");
+ issue = createIssue(11L, "Test issue", ISSUE, null, null, null, null,
null,
+ new BasicWatchers(watchersUri, true, 0));
+ Promise<Void> rejected = Promises.rejected(rejection);
+
+
lenient().when(jiraRestClientFactory.createWithBasicHttpAuthentication(any(),
any(), any())).thenReturn(jiraClient);
+
lenient().when(jiraClient.getIssueClient()).thenReturn(issueRestClient);
+ // reading and creating an issue work
+
lenient().when(issueRestClient.getIssue(anyString())).thenReturn(Promises.promise(issue));
+ lenient().when(issueRestClient.createIssue(any(IssueInput.class)))
+ .thenReturn(Promises.promise(new BasicIssue(issue.getSelf(),
ISSUE, 11L)));
+ // every request that changes the issue is rejected
+ lenient().when(issueRestClient.addComment(any(),
any(Comment.class))).thenReturn(rejected);
+
lenient().when(issueRestClient.linkIssue(any(LinkIssuesInput.class))).thenReturn(rejected);
+ lenient().when(issueRestClient.addWorklog(any(),
any(WorklogInput.class))).thenReturn(rejected);
+ lenient().when(issueRestClient.addAttachments(any(),
any(File.class))).thenReturn(rejected);
+ lenient().when(issueRestClient.addAttachment(any(),
any(InputStream.class), anyString())).thenReturn(rejected);
+ lenient().when(issueRestClient.transition(any(Issue.class),
any(TransitionInput.class))).thenReturn(rejected);
+ lenient().when(issueRestClient.addWatcher(any(),
anyString())).thenReturn(rejected);
+ lenient().when(issueRestClient.removeWatcher(any(),
anyString())).thenReturn(rejected);
+
+ CamelContext camelContext = super.createCamelContext();
+ camelContext.disableJMX();
+ camelContext.addComponent(JIRA, new JiraComponent(camelContext));
+ return camelContext;
+ }
+
+ @Test
+ void addCommentFails() {
+ assertRejected("addComment", "A comment", Map.of(ISSUE_KEY, ISSUE));
+ }
+
+ @Test
+ void addIssueLinkFails() {
+ assertRejected("addIssueLink", null,
+ Map.of(PARENT_ISSUE_KEY, ISSUE, CHILD_ISSUE_KEY, KEY + "-12",
LINK_TYPE, "Relates"));
+ }
+
+ @Test
+ void addWorkLogFails() {
+ assertRejected("addWorkLog", "A comment", Map.of(ISSUE_KEY, ISSUE,
MINUTES_SPENT, 10));
+ }
+
+ @Test
+ void attachAFileFails(@TempDir Path dir) throws Exception {
+ File file = Files.writeString(dir.resolve("attachment.txt"),
"content").toFile();
+
+ assertRejected("attach", file, Map.of(ISSUE_KEY, ISSUE));
+ }
+
+ @Test
+ void attachAStreamFails() {
+ InputStream body = new
ByteArrayInputStream("content".getBytes(StandardCharsets.UTF_8));
+
+ assertRejected("attach", body, Map.of(ISSUE_KEY, ISSUE,
Exchange.FILE_NAME, "attachment.txt"));
+ }
+
+ @Test
+ void transitionIssueFails() {
+ assertRejected("transitionIssue", null, Map.of(ISSUE_KEY, ISSUE,
ISSUE_TRANSITION_ID, 3));
+ }
+
+ @Test
+ void transitionIssueFailsWhenItsCommentIsRejected() {
+ // partial failure: Jira makes the transition and then rejects the
comment (the message body)
+ when(issueRestClient.transition(any(Issue.class),
any(TransitionInput.class))).thenReturn(Promises.promise(null));
+
+ assertRejected("transitionIssue", "A comment", Map.of(ISSUE_KEY,
ISSUE, ISSUE_TRANSITION_ID, 3));
+
+ // the transition was requested before the comment
+ InOrder inOrder = inOrder(issueRestClient);
+ inOrder.verify(issueRestClient).transition(same(issue),
any(TransitionInput.class));
+ inOrder.verify(issueRestClient).addComment(any(), any(Comment.class));
+ }
+
+ @Test
+ void addWatcherFails() {
+ assertRejected("watchers", null, Map.of(ISSUE_KEY, ISSUE,
ISSUE_WATCHERS_ADD, "someone"));
+ }
+
+ @Test
+ void removeWatcherFails() {
+ assertRejected("watchers", null, Map.of(ISSUE_KEY, ISSUE,
ISSUE_WATCHERS_REMOVE, "someone"));
+ }
+
+ @Test
+ void addIssueWithAWatcherFails() {
+ assertRejected("addIssue", "A description",
+ Map.of(ISSUE_PROJECT_KEY, KEY, ISSUE_TYPE_ID, 1L,
ISSUE_SUMMARY, "A summary", ISSUE_WATCHERS_ADD, "someone"));
+ }
+
+ @Test
+ void addIssueKeepsTheCreatedIssueWhenAWatcherFails() {
+ Exchange exchange = template.request("direct:addIssue", e -> {
+ e.getIn().setBody("A description");
+ e.getIn().setHeaders(new HashMap<>(
+ Map.of(ISSUE_PROJECT_KEY, KEY, ISSUE_TYPE_ID, 1L,
ISSUE_SUMMARY, "A summary", ISSUE_WATCHERS_ADD,
+ "someone")));
+ });
+
+ assertSame(rejection, exchange.getException());
+ // the issue exists in Jira, so the exchange must carry it for the
error handling of the route
+ assertSame(issue, exchange.getMessage().getBody());
+ }
+
+ private void assertRejected(String type, Object body, Map<String, Object>
headers) {
+ Exchange exchange = template.request("direct:" + type, e -> {
+ e.getIn().setBody(body);
+ e.getIn().setHeaders(new HashMap<>(headers));
+ });
+
+ assertSame(rejection, exchange.getException());
+ }
+
+ @Override
+ protected RouteBuilder createRouteBuilder() {
+ return new RouteBuilder() {
+ @Override
+ public void configure() {
+ for (String type : new String[] {
+ "addComment", "addIssueLink", "addWorkLog", "attach",
"transitionIssue", "watchers", "addIssue" }) {
+ from("direct:" + type).to("jira://" + type + "?jiraUrl=" +
JIRA_CREDENTIALS);
+ }
+ }
+ };
+ }
+}
diff --git
a/components/camel-jira/src/test/java/org/apache/camel/component/jira/producer/TransitionIssueProducerTest.java
b/components/camel-jira/src/test/java/org/apache/camel/component/jira/producer/TransitionIssueProducerTest.java
index 12ae454fc6ae..a399301f4345 100644
---
a/components/camel-jira/src/test/java/org/apache/camel/component/jira/producer/TransitionIssueProducerTest.java
+++
b/components/camel-jira/src/test/java/org/apache/camel/component/jira/producer/TransitionIssueProducerTest.java
@@ -90,7 +90,7 @@ public class TransitionIssueProducerTest extends
CamelTestSupport {
Status status = new Status(doneStatusUri, 1L, "Done", "Done",
null, null);
Resolution resolution = new Resolution(doneResolutionUri, 31L,
"Resolution", "Resolution");
issue = transitionIssueDone(issue, status, resolution);
- return null;
+ return Promises.promise(null);
});
}
diff --git
a/components/camel-jira/src/test/java/org/apache/camel/component/jira/producer/WatcherProducerTest.java
b/components/camel-jira/src/test/java/org/apache/camel/component/jira/producer/WatcherProducerTest.java
index 19a6b26131df..77b718a2a523 100644
---
a/components/camel-jira/src/test/java/org/apache/camel/component/jira/producer/WatcherProducerTest.java
+++
b/components/camel-jira/src/test/java/org/apache/camel/component/jira/producer/WatcherProducerTest.java
@@ -106,7 +106,7 @@ public class WatcherProducerTest extends CamelTestSupport {
backendIssue = createIssue(backendIssue.getId(),
backendIssue.getSummary(), backendIssue.getKey(),
backendIssue.getIssueType(), backendIssue.getDescription(),
backendIssue.getPriority(), backendIssue.getAssignee(),
null, basicWatchers);
- return null;
+ return Promises.promise(null);
});
lenient().when(issueRestClient.removeWatcher(any(URI.class),
anyString())).then(inv -> {
String username = inv.getArgument(1);
@@ -115,7 +115,7 @@ public class WatcherProducerTest extends CamelTestSupport {
backendIssue = createIssue(backendIssue.getId(),
backendIssue.getSummary(), backendIssue.getKey(),
backendIssue.getIssueType(), backendIssue.getDescription(),
backendIssue.getPriority(), backendIssue.getAssignee(),
null, basicWatchers);
- return null;
+ return Promises.promise(null);
});
lenient().when(issueRestClient.getIssue(anyString())).then(inv ->
Promises.promise(backendIssue));
lenient().when(issueRestClient.getWatchers(any(URI.class))).then(inv
-> {