Files
gitea-axi/.claude/tasks/0015-label-commands.md
alexion 03937a8f6e
Some checks failed
CI / test (pull_request) Successful in 50s
CI / test (push) Has been cancelled
test: add label e2e coverage and fixture-tier backfill (task 0015)
2026-07-14 07:35:45 -04:00

53 lines
5.1 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
spec: gitea-axi
blocked-by: 0004-issue-create-and-comment
---
## What to build
The label command group: `label list`, `label create`, `label edit`, `label delete`.
`label list` takes `--limit` (default 500) and outputs a count line plus `labels: [ { name } ]`.
`label create` requires `--name` and `--color` (hex without `#`; the `#` is prepended before the API call) with optional `--description`; it is idempotent via a case-insensitive existence check, reporting `create: already_exists` instead of failing.
`label edit <name>` and `label delete <name>` resolve the positional name via the standard case-insensitive label lookup with `VALIDATION_ERROR` when not found.
`label delete` is deliberately not idempotent — a nonexistent label is refused rather than reported as success (see ADR 0010).
## Acceptance criteria
- [x] `label list` renders the count line and `labels:` block; empty repos get the explicit empty state
- [x] `label create --name --color` creates the label, prepending `#` to the color, and outputs `created: ok` + `label: <name>`
- [x] Creating an existing label (case-insensitive) outputs `create: already_exists` + the existing name, exit 0
- [x] `label edit <name>` applies `--name`/`--color`/`--description` and outputs `edit: ok` + the resulting name
- [x] `label edit`/`label delete` on an unknown name yield `VALIDATION_ERROR` (exit 2)
- [x] `label delete <name>` outputs `delete: ok` + `label: <name>`
- [x] Fixture-server tests cover create, idempotent re-create, edit, delete, and the unknown-name refusals
- [x] End-to-end tests exercise the live-Gitea semantics the fixture server cannot attest to: the `#`-prefixed color round-trip on create, idempotency against the live listing, and name→id resolution behind edit/delete (plus the not-idempotent delete refusal)
## Implementation Notes
Built the `label` group in `src/commands/label.ts`, wired into `src/cli.ts` (dispatcher plus the two most-common entries in the top-level help), following the sibling `issue`/`pr` command patterns.
Deviations and decisions:
- **`label edit` requires at least one change.**
The spec/gh-axi reference leaves all edit flags optional, but sending an empty `PATCH` is a pointless call, so an edit with none of `--name`/`--color`/`--description` is refused with `VALIDATION_ERROR` — mirroring `issue edit`'s "requires at least one change" guard for consistency within gitea-axi.
- **Resulting name for `edit`/`create`/`delete` comes from the API response** (`edited.name`, `label.name`), not the input, so the reported name is the server's canonical echo (correct casing, and the unchanged original when `--name` was omitted).
- **`create` vs `created` output keys.**
The success key is `created: ok` and the idempotent-hit key is `create: already_exists` — two different top-level keys.
This is spec-mandated (spec lines 342343) rather than the `already: true` shape the dependency no-ops use; kept verbatim to match the fixed contract.
- **Flat `renderObject` output shape.**
Added `renderObject(item, help)` to `src/render.ts` because the label create/edit/delete outputs are flat top-level fields (`created: ok` / `label: <name>`), which the sibling `renderDetail` (nests under a `noun:` block) cannot produce. This is the spec's output shape, not a new convention chosen freely.
- **Shared lookup + positional helpers (cleanups from review).**
Extracted `findLabel`/`resolveLabel` and a shared `labelNotFound` message into `src/lookup.ts`, reused by the existing `resolveLabelIds`; and extracted `parseSinglePositional` in `src/flags.ts`, now shared by `parsePositionalNumber` and the label name positionals, removing the duplicated count-check/error scaffolding.
- **`label list` uses a single-page fetch with `--limit` (default 500)**, reading `X-Total-Count` for the count line, rather than the exhaustive pagination `resolveLabel`/`resolveLabelIds` use; the spec asks only for `--limit`, and a repo with >500 labels is signalled by the `count: N of T total` line.
### Follow-ups added after review
- **End-to-end tier extended.**
The task originally scoped its tests to the fixture-server tier only, matching the precedent of the preceding PR-command tasks (00110014), none of which added e2e cases.
On reflection the label commands sit squarely inside the e2e tier's charter — "behavior the fixture server cannot attest to" — because a fixture server never enforces that Gitea's `CreateLabelOption.color` requires the leading `#`, nor that edit/delete really key on the numeric label id.
Added a `test/e2e/mutations.test.ts` label-lifecycle case (create → idempotent re-create → edit → delete, verified against live state via a new `fetchLabels` provisioner helper) plus the not-idempotent delete refusal.
These run only in CI (gated on `GITEA_AXI_E2E_URL`).
- **Unit-tier coverage backfill.**
The initial fixture tests covered only the happy paths and unknown-name refusals, leaving the help output, validation errors, and API-error propagation untested — enough to drop the repo below its global branch-coverage gate.
Added confirming fixture tests for those behaviors, bringing `src/commands/label.ts` to ~96% line / ~86% branch and the suite back over its thresholds.