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)

Reply via email to