shashank created CAMEL-25544:
--------------------------------
Summary: camel-jira - the addComment, addIssueLink, addWorkLog,
attach, deleteIssue, transitionIssue and watchers producers do not wait for the
answer of Jira, so a rejected request is not noticed and the exchange completes
as if it had succeeded
Key: CAMEL-25544
URL: https://issues.apache.org/jira/browse/CAMEL-25544
Project: Camel
Issue Type: Bug
Components: camel-jira
Reporter: shashank
Assignee: shashank
The Jira REST client is asynchronous: the methods of {{IssueRestClient}} return
a {{Promise}}, and {{claim()}} waits for the answer and throws
{{RestClientException}} when Jira rejected the request. {{AddIssueProducer}}
({{createIssue(...).claim()}}) and {{UpdateIssueProducer}}
({{updateIssue(...).claim()}}) do that. The other producers that change an
issue drop the promise (lines at main 9432ba16d026):
{code:java}
issueClient.transition(issue, transitionInput); //
TransitionIssueProducer:57
issueClient.addComment(commentsUri, Comment.valueOf(commentStr)); //
TransitionIssueProducer:63
issueClient.addComment(commentsUri, comment); //
AddCommentProducer:57
issueClient.linkIssue(linkIssuesInput); //
AddIssueLinkProducer:63
issueClient.addWorklog(issue.getWorklogUri(), worklogInput); //
AddWorkLogProducer:60
issueClient.addAttachments(attachmentsUri, file); //
AttachFileProducer:70
issueClient.addAttachment(attachmentsUri, is, name); //
AttachFileProducer:72
issueClient.deleteIssue(issueKey, true); //
DeleteIssueProducer:42
issueClient.addWatcher(...); issueClient.removeWatcher(...); //
WatcherProducer:57,66 and AddIssueProducer:125
{code}
So {{process()}} returns at once, and:
* a request that Jira rejects (no permission, a transition that is not allowed
in the workflow, a missing mandatory field, an unknown link type or user, an
attachment that is too large, HTTP 401/403/5xx, a network error) is not
noticed: the exchange completes successfully, nothing is logged, and the
comment, link, work log, attachment, transition or deletion is missing in Jira;
* the route continues before Jira has done the work, so a following step
({{fetchIssue}}, a second {{transitionIssue}}) can see the old state, and the
comment of {{transitionIssue}} is not ordered after the transition;
* for {{attach}}, the route continues before the upload has finished.
h3. Reproduction
New {{DeleteIssueProducerFailureTest}} uses the real Jira client against a
local HTTP server that answers the {{DELETE}} with 403 and a Jira error body:
on main the exchange has no exception, and the request has not even reached the
server when the exchange is done:
{noformat}
DeleteIssueProducerFailureTest.failsTheExchangeWhenJiraRejectsTheRequest:
expected: <[DELETE /rest/api/latest/issue/TST-1]> but was: <[]>
{noformat}
New {{JiraProducerRejectedRequestTest}} (mocked {{IssueRestClient}} as in the
other producer tests; every request that changes the issue returns a rejected
promise): one test per producer ({{addComment}}, {{addIssueLink}},
{{addWorkLog}}, {{attach}} with a file and with a stream, {{transitionIssue}},
{{watchers}} add and remove, {{addIssue}} with a watcher) expects the
{{RestClientException}} on the exchange. On main, twice, all 11 new tests fail
(the 9 mocked ones with {{expected: <RestClientException...> but was: <null>}}).
h3. Proposed fix
The producers claim the promise of each request, as {{AddIssueProducer}} and
{{UpdateIssueProducer}} do. A rejected request then fails the exchange with the
{{RestClientException}} of the client, and the route continues when Jira has
answered (for {{attach}}: when the upload has finished). {{AddIssueProducer}}
sets the created issue on the exchange before it adds the watchers, so that it
is there when adding a watcher fails. The mocks of the existing producer tests
returned {{null}} for these calls and now return a completed promise. 4.23
upgrade guide note added (routes that relied on failures being ignored must
handle the exception). camel-jira with the fix: 58 tests, 0 failures.
Found by reading the producers next to each other ({{updateIssue}} claims,
{{transitionIssue}} does not) and with a small TLA+ model of one exchange
(send, answer by Jira, return of {{process()}}, close of the body): on main "a
successful exchange did not have its request rejected" is violated in 4 steps,
"the route continues only when Jira has answered" in 3 steps; with the fix both
properties hold. Confirmed with the real client. (The model also has a property
about an attachment that is read after the exchange released it, violated in 5
steps; no test with the real client shows it, so it is not claimed here.)
Affected: main, camel-4.22.x, camel-4.18.x, camel-4.14.x (same code, GitHub
contents API).
Duplicate check (2026-10-11): JIRA component camel-jira with text "claim",
"Promise", "asynchronous", "silently" or "swallow" (2, unrelated), component
camel-jira since 2022 (15, none about producer results; CAMEL-23539, the open
upgrade of the Jira client, does not change these calls); GitHub pull requests
with "jira" in the title: none open. No open pull request changes the
camel-jira producers.
_Filed with Claude Code on behalf of allthingssecurity._
--
This message was sent by Atlassian Jira
(v8.20.10#820010)