@@ -183,3 +183,121 @@ agent = Agent(
183183path, timeouts, and retries. The module exports ` default_config ` ; pass a custom
184184` Config ` to ` JiraClient ` , ` build_mcp_server ` , or the app environment when you
185185need it.
186+
187+ ## Testing
188+
189+ An end-to-end pass against a real Jira Cloud site. Use a scratch project —
190+ step 4 creates and comments on real issues.
191+
192+ ** 1. Create the credentials.** An API token from
193+ < https://id.atlassian.com/manage-profile/security/api-tokens > . Jira uses basic
194+ auth over your account email plus the token, so all three values are secrets:
195+
196+ ``` bash
197+ flyte create secret JIRA_BASE_URL --value https://< your-site> .atlassian.net
198+ flyte create secret JIRA_EMAIL --value you@example.com
199+ flyte create secret JIRA_API_TOKEN --value < api-token>
200+ flyte create secret JIRA_WEBHOOK_TOKEN --value < random-string>
201+ ```
202+
203+ ` JIRA_WEBHOOK_TOKEN ` is a value you invent. Jira webhooks are ** not signed** ,
204+ so the receiver authenticates them with this shared token instead — see step 6.
205+
206+ ** 2. Check the client works before involving the platform:**
207+
208+ ``` bash
209+ export JIRA_BASE_URL=https://< your-site> .atlassian.net
210+ export JIRA_EMAIL=you@example.com
211+ export JIRA_API_TOKEN=< api-token>
212+ python -c "
213+ from flyteplugins.jira import JiraClient
214+ with JiraClient() as c:
215+ print(c.get_myself()['displayName'])
216+ print([p['key'] for p in c.list_projects()])
217+ "
218+ ```
219+
220+ A 401 here is almost always the email/token pair rather than the token alone.
221+
222+ ** 3. Deploy the task the webhook will launch.**
223+
224+ ``` bash
225+ flyte deploy plugins/jira/examples/manage_ticket.py env
226+ ```
227+
228+ ` react_to_jira_events.py ` looks this task up by name (` triage_issue ` ), so it
229+ has to exist before the app can launch it.
230+
231+ ** 4. Run a task directly** , to confirm writes land before any webhook is
232+ involved:
233+
234+ ``` bash
235+ flyte run plugins/jira/examples/manage_ticket.py open_ticket \
236+ --project_key < PROJ> --summary " Flyte test issue" --description " created by the plugin test"
237+ ```
238+
239+ ** 5. Deploy the webhook app.**
240+
241+ ``` bash
242+ python plugins/jira/examples/react_to_jira_events.py
243+ ```
244+
245+ It prints the app URL. Open it: the dashboard should show all four secrets
246+ mounted, and * Verify Jira credentials* should return your display name.
247+
248+ ** 6. Point Jira at the app.** Jira Settings → System → Webhooks → * Create a
249+ Webhook* :
250+
251+ - URL: ` <app-url>/webhook `
252+ - Events: * Issue created* and * Issue updated*
253+
254+ Jira cannot add custom headers, and it does not sign its webhooks — so the
255+ ` X-Webhook-Token ` header the receiver requires has to be injected by whatever
256+ sits in front of the app (an API gateway, a reverse proxy, or a small
257+ forwarder). For a first local test, deploy the app with
258+ ` require_webhook_token=False ` and protect it at the network level instead,
259+ then send a delivery by hand to confirm the path works:
260+
261+ ``` bash
262+ curl -X POST < app-url> /webhook \
263+ -H ' Content-Type: application/json' \
264+ -H ' X-Webhook-Token: <the value you chose>' \
265+ -d ' {"webhookEvent":"jira:issue_created","issue":{"key":"PROJ-1","fields":{"summary":"hand-made","project":{"key":"PROJ"}}}}'
266+ ```
267+
268+ ** 7. Trigger a real event.** Create an issue in the test project. Then check,
269+ in order:
270+
271+ - ` <app-url>/api/events ` — the normalized event, ` qualified_type ` of
272+ ` jira:issue_created ` .
273+ - ` flyte get runs ` — a run whose ` dedupe ` label matches.
274+ - The issue — the triage task's comment.
275+
276+ ** 8. Confirm idempotency.** The dedupe key folds in Jira's event timestamp, so
277+ a redelivered event dedupes while a later update to the same issue gets its own
278+ run.
279+
280+ ** 9. Optional — the allowlist.** Redeploy with ` project_keys=["<PROJ>"] ` and
281+ create an issue in another project. The receiver should answer 200 with a
282+ ` skipped ` message. The allowlist fails closed, so an event carrying no project
283+ key is skipped too.
284+
285+ ** 10. Optional — the MCP server.**
286+
287+ ``` bash
288+ python plugins/jira/examples/jira_mcp_server.py
289+ claude mcp add --transport http jira-mcp < app-url> /mcp/mcp
290+ ```
291+
292+ Ask an agent to summarize open bugs in the project. The default surface is
293+ read-only.
294+
295+ ### Troubleshooting
296+
297+ | Symptom | Cause |
298+ | --- | --- |
299+ | Delivery returns 401 | The ` X-Webhook-Token ` header is missing or does not match ` JIRA_WEBHOOK_TOKEN ` . Jira alone cannot send it — see step 6. |
300+ | Delivery returns 503 | ` JIRA_WEBHOOK_TOKEN ` is not mounted; check ` /api/status ` . |
301+ | Client raises 401 | Basic auth needs the * email + token* pair, not the token alone. |
302+ | A description shows as ` [object Object] ` | Jira v3 stores rich text as ADF; the client converts plain strings for you, so pass a plain string. |
303+ | 200 but no run | No handler matched, or the allowlist skipped it — the response body says which. |
0 commit comments