feat: implement four issue mutation commands (PR 4/5) #15

Merged
David merged 1 commit from feat/issue-mutations into main 2026-05-14 00:15:54 +02:00
Owner

Summary

PR 4 of 5. Adds POST support to the HTTP client and ships the four mutating issue subcommands.

  • Client::post_ignore: POSTs a JSON body with Bearer auth, errors on non-2xx with status + body (same shape as get_json), drains the response so the connection can be reused. A post_json variant is intentionally not added: every mutation here only cares about success/failure, and the YouTrack response shapes we'd parse are thrown away by the Go CLI too.
  • yt::query::build_apply_sprint_command: builds the /api/commands query (Board "<board>" "<sprint>") using Go-style quoting so embedded " and \ escape correctly.
  • yt::api::{apply_command, set_issues_sprint, add_comment, update_state, update_estimation}: each mirrors the Go body byte-for-byte (StateIssueCustomField and PeriodIssueCustomField shapes included).
  • issue comment <id> <text...>: joins trailing args with spaces, posts to /api/issues/<id>/comments, prints Comment added to <id>..
  • issue set-state <id> <state...>: joins state words, POSTs the StateIssueCustomField body.
  • issue set-estimation <id> <minutes>: rejects 0 explicitly (Go silently sends 0m; we surface the validation since the user hasn't asked us to clear estimations that way).
  • issue sprint set <ids...>: ports DetermineSprint resolution. --sprint flag, then default_sprint config, then list the board's sprints and pick the current one via determine_current_sprint_now. Errors when none of the three turn up a sprint.

Verification

  • cargo fmt --check clean.
  • cargo clippy --all-targets -- -D warnings clean.
  • cargo test --all-targets: 112 tests pass (27 new on this branch).

Test coverage on this PR: wiremock-asserted POST body shape for every helper (including the quoted-and-escaped command query), HTTP error propagation, missing-credentials guard on each command, every input-validation path (set_issues_sprint rejects empty board / empty sprint / empty issue list; set-estimation rejects 0), and the full board + sprint resolution chain for sprint set (explicit flags skip lookup; falls back to default_sprint; falls back to current sprint from board; errors when neither board nor current sprint is known).

Test plan

  • youtrack-cli issue comment <ID> "hello" posts a real comment.
  • youtrack-cli issue set-state <ID> "In Progress" flips the State custom field.
  • youtrack-cli issue set-estimation <ID> 75 writes 75m on the issue.
  • youtrack-cli issue sprint set <ID1> <ID2> --board "X" --sprint "Sprint 26" moves both issues onto the named sprint.
  • Same command without --sprint (with a configured default_sprint) writes the configured sprint.
  • Same command with neither flag nor default_sprint: succeeds when the board has a current sprint; errors clearly otherwise.

Sequence

This is PR 4 of 5. PR 5 picks up work add, work check, and issue daily-sync.

## Summary PR 4 of 5. Adds POST support to the HTTP client and ships the four mutating issue subcommands. - `Client::post_ignore`: POSTs a JSON body with Bearer auth, errors on non-2xx with status + body (same shape as `get_json`), drains the response so the connection can be reused. A `post_json` variant is intentionally not added: every mutation here only cares about success/failure, and the YouTrack response shapes we'd parse are thrown away by the Go CLI too. - `yt::query::build_apply_sprint_command`: builds the `/api/commands` query (`Board "<board>" "<sprint>"`) using Go-style quoting so embedded `"` and `\` escape correctly. - `yt::api::{apply_command, set_issues_sprint, add_comment, update_state, update_estimation}`: each mirrors the Go body byte-for-byte (`StateIssueCustomField` and `PeriodIssueCustomField` shapes included). - `issue comment <id> <text...>`: joins trailing args with spaces, posts to `/api/issues/<id>/comments`, prints `Comment added to <id>.`. - `issue set-state <id> <state...>`: joins state words, POSTs the `StateIssueCustomField` body. - `issue set-estimation <id> <minutes>`: rejects `0` explicitly (Go silently sends `0m`; we surface the validation since the user hasn't asked us to clear estimations that way). - `issue sprint set <ids...>`: ports `DetermineSprint` resolution. `--sprint` flag, then `default_sprint` config, then list the board's sprints and pick the current one via `determine_current_sprint_now`. Errors when none of the three turn up a sprint. ## Verification - `cargo fmt --check` clean. - `cargo clippy --all-targets -- -D warnings` clean. - `cargo test --all-targets`: 112 tests pass (27 new on this branch). Test coverage on this PR: wiremock-asserted POST body shape for every helper (including the quoted-and-escaped command query), HTTP error propagation, missing-credentials guard on each command, every input-validation path (`set_issues_sprint` rejects empty board / empty sprint / empty issue list; `set-estimation` rejects 0), and the full board + sprint resolution chain for `sprint set` (explicit flags skip lookup; falls back to `default_sprint`; falls back to current sprint from board; errors when neither board nor current sprint is known). ## Test plan - [ ] `youtrack-cli issue comment <ID> "hello"` posts a real comment. - [ ] `youtrack-cli issue set-state <ID> "In Progress"` flips the State custom field. - [ ] `youtrack-cli issue set-estimation <ID> 75` writes 75m on the issue. - [ ] `youtrack-cli issue sprint set <ID1> <ID2> --board "X" --sprint "Sprint 26"` moves both issues onto the named sprint. - [ ] Same command without `--sprint` (with a configured `default_sprint`) writes the configured sprint. - [ ] Same command with neither flag nor `default_sprint`: succeeds when the board has a current sprint; errors clearly otherwise. ## Sequence This is PR 4 of 5. PR 5 picks up `work add`, `work check`, and `issue daily-sync`.
feat: implement four issue mutation commands
All checks were successful
Check / fmt + clippy + build + tests (pull_request) Successful in 8s
Create release / Create release from merged PR (pull_request) Has been skipped
61b6b95ee2
PR 4 of 5. Adds POST support to the YT client and ships the four mutating issue subcommands. `Client::post_ignore` rounds out the HTTP wrapper: it POSTs a JSON body with Bearer auth, surfaces non-2xx as `ClientError::Status` (same shape as the GET path), and consumes the response body without parsing it so the connection can be reused. The post-with-decode variant is intentionally not introduced yet: every mutation here cares only about success / failure, and the Go endpoints we mirror return shapes we'd promptly throw away. New `yt::query::build_apply_sprint_command` builds the `/api/commands` query (`Board "<board>" "<sprint>"`) using Go-style quoting so embedded quotes and backslashes escape correctly. New `yt::api` helpers compose the actual mutations: `apply_command` POSTs the command body to `/api/commands`; `set_issues_sprint` validates inputs (non-empty board, sprint, and at least one issue id) and delegates to `apply_command`; `add_comment` posts `{"text": ...}` to the comments endpoint; `update_state` posts a `StateIssueCustomField` shape; `update_estimation` posts a `PeriodIssueCustomField` with the `Xm` presentation. Each mirrors the Go body byte-for-byte. The four command modules are thin wrappers over those helpers. `issue comment <id> <text...>` joins trailing args with spaces and prints `Comment added to <id>.`. `issue set-state <id> <state...>` joins state words. `issue set-estimation <id> <minutes>` rejects 0 explicitly (Go silently sends `0m`, which the user has not chosen yet). `issue sprint set <ids...>` ports `DetermineSprint`'s 3-tier resolution: `--sprint` flag, then `default_sprint` config, then list the board's sprints and pick the current one via the existing `determine_current_sprint_now`. Errors when neither the flag nor config nor a current sprint can be found. Tests with wiremock assert the exact POST body for each helper (including the quoted-and-escaped variants of the sprint command), HTTP error propagation, missing-credentials guard, all input-validation paths, and the full board+sprint resolution chain for `sprint set`. 112 tests pass; clippy clean; fmt clean.
David merged commit ff07914485 into main 2026-05-14 00:15:54 +02:00
David deleted branch feat/issue-mutations 2026-05-14 00:15:54 +02:00
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
pandoras-box/youtrack-cli!15
No description provided.