Skip to content

test(cmd): pin the machine-facing JSON output contracts - #66

Open
aaearon wants to merge 7 commits into
test/argument-capturefrom
test/output-contracts
Open

test(cmd): pin the machine-facing JSON output contracts#66
aaearon wants to merge 7 commits into
test/argument-capturefrom
test/output-contracts

Conversation

@aaearon

@aaearon aaearon commented Aug 15, 2026

Copy link
Copy Markdown
Owner

Part 5 of 8. Base: test/argument-capture (#65) — it provides the capture infrastructure. #63 must merge first.

Why

--output json is the documented machine/LLM-facing surface, and it was barely asserted. All of these shipped green: provider case flipped, duration zeroed, roleId/workspaceId blanked, cloud-vs-group type swapped, target/role swapped, SecretAccessKey/SessionToken swapped, and DirectoryID dropped from group favorites on both write paths.

Note the asymmetry that existed: the env text path was pinned by TestEnvCommand_AWSSuccess; the env JSON path with identical data was not.

What

assertJSONEqual — whole-object comparison for one representative contract test per output, where brittleness against added fields is desirable because it forces a conscious compatibility review. Focused tests retained for conditional fields. Fixture values are deliberately distinguishable (a swap of two identical values is undetectable), with a comment saying so.

35 mutations, all killed.

One production change, and why the plan's "no production code" rule was overridden

TestListJSON_RoundTripsToRequestSubmit originally stubbed its second leg with a textual copy of the production matcher. A copy pins nothing — mutating request_submit.go to resolve on workspaceId instead of the emitted name survived the whole cmd suite, while the test's name, its comment, and the ledger all claimed that guarantee was proven.

Fixed properly: matchWorkspaceByName extracted and called from both. The mutation now fails. Recording a guarantee that does not exist is worse than an open row.

Honest limits

  • remainingSeconds derives from time.Now(), so it is range-checked then pinned before the whole-object compare. Verified this still catches a dropped field, a renamed tag, a wrong type, and a sixfold unit error.
  • The round trip's leg 2 still stubs the eligibility fetch (it would need live SCA auth). It pins resolution logic, not the fetch path.
  • TestListCommand_RefreshFlagRegistered covers registration and parsing only — the --refreshbuildCachedLister wiring sits behind bootstrapSCAService and is unreachable from unit tests. The test comment and ledger both say so. (--refresh is not a production no-op; that earlier claim was refuted.)
  • An off-by-one guard (len(fields) < 6 before reading fields[6]) made a test panic under a real regression, which aborts the whole cmd binary. Fixed to < 7.

Adversarial review performed (Codex credits exhausted; review by a Claude agent). Both blocking findings fixed.

@aaearon aaearon closed this Aug 15, 2026
@aaearon aaearon reopened this Aug 15, 2026
@aaearon aaearon closed this Aug 15, 2026
@aaearon aaearon reopened this Aug 15, 2026
@aaearon
aaearon force-pushed the test/argument-capture branch from 63633cf to b69451d Compare August 16, 2026 07:42
Add assertJSONEqual and one whole-object contract test per machine-facing
document: status, list, cloud and group elevation, env credentials and the
favorites list. Inline expected JSON, deliberately brittle against added
fields so a new field forces a compatibility review.

Also add the list -> request submit round trip: the emitted target NAME feeds
--target and the emitted roleId feeds --role-id, and both must resolve back to
the same eligible target.

Fixture values are all distinct and self-describing on purpose; a swap
mutation is invisible when both sides hold the same string.
- favorites: DirectoryID is persisted on both the unified-selector and the
  --type groups path, verified by resolving the saved favorite back through
  findMatchingGroup against two same-named groups in different directories
- favorites: non-default default_provider, --provider precedence over the
  target CSP, parseFavoritesAddFlags validation, remove arity
- list: assert Cobra's actual mutual-exclusion error (the old test passed on
  an unrelated runtime error), --provider suppresses groups, --refresh
- status: directory-name merge precedence, stale session-timestamp cleanup,
  and an exact "remaining: 45m" instead of a prefix a sixfold error satisfied
- request: text and JSON field mappings (target/role columns, created vs
  updated attribution, timeFrom/timeTo)
All 32 PR5 rows plus OUT-27 reverified with -count=1: mutation applied, test
fails, mutation reverted, test passes. Test-name and scope corrections noted
in the rows where the plan's placeholder name or fixture did not survive
contact (OUT-09/10 fold into the status contract test, OUT-23's fixture, and
OUT-25, which covers flag registration rather than a cache bypass).

Record the output-contract and distinct-fixture conventions in CLAUDE.md.
… panic

Three adversarial-review findings on PR5's output contracts.

1. TestListJSON_RoundTripsToRequestSubmit's second leg stubbed
   resolveSubmitTargetFn with a textual copy of the resolveSubmitTarget
   matcher, so it pinned nothing: changing --target to resolve on
   WorkspaceID instead of WorkspaceName survived the whole cmd suite.
   Extract matchWorkspaceByName (behaviour-preserving) and call it from
   both production and the test, so the mutation now fails.

2. TestRequestList_TextFieldMapping guarded len(fields) < 6 while
   indexing fields[6]; a column-blanking mutation panicked and aborted
   the entire cmd test binary. Raise the guard to < 7.

3. Narrow pinRemainingSeconds' lower bound from 2600 to 2695 so a
   whole-minute arithmetic error dies at the pin itself, and label the
   deliberately impossible favorites provider/CSP fixture as such.
Both machine-facing documents were entirely unpinned: every test unmarshalled
into the very output struct under test, so renaming requests, sessionId or
outcome survived. outcome is the single classification field callers switch on.
Every fixture populated credentials, workspaceName, username and directory, so
dropping omitempty from any of them was invisible. Assert the key is absent,
not null or empty.
Name the nine pinned documents instead of claiming blanket coverage, and add
OUT-30..38 to the mutation ledger.
@aaearon
aaearon force-pushed the test/output-contracts branch from 59776c2 to c2704f8 Compare August 16, 2026 07:44
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant