Summary
The CLI now delegates migrated Cloud commands through clickhouse-cloud-api. The library tests already prove the wire contract: HTTP method, path, auth, and struct → JSON serialization. The remaining useful CLI-specific gap is narrower:
- Do clap-parsed flags still have the intended names, types, defaults, and repeatability?
- Do CLI request builders populate the intended
clickhouse-cloud-api request structs?
This issue adds that coverage without adding another CLI-layer wiremock or JSON snapshot suite.
Current State
crates/clickhousectl/src/cloud/commands.rs already has partial unit coverage for all 8 existing build_*_request helpers. The gap is not first-time coverage; it is systematic coverage: minimal + maximal inputs with direct assertions on every relevant library struct field.
crates/clickhousectl/src/cloud/cli.rs already has some Cli::try_parse_from tests, but they are selective and mostly prove clap parsing only.
service scale does not currently use a helper — it constructs ServiceReplicaScalingPatchRequest inline in service_scale (commands.rs:2050-2059). Three Option<u32> fields all using .map(f64::from) make this the most accident-prone construction in the file. This issue extracts it into a build_service_scale_request helper for parity and testability.
Three other body-building handlers also construct request structs inline and have no unit coverage:
member_update (commands.rs:2492) builds MemberPatchRequest. Maps the repeatable --role-id Vec to assigned_role_ids, collapses an empty Vec to None, and hardcodes role: None.
invitation_create (commands.rs:2582) builds InvitationPostRequest. Maps --email → email, repeatable --role-id → assigned_role_ids, uses InvitationPostRequestRole::default().
private_endpoint_create (commands.rs:2284) builds ServicPrivateEndpointePostRequest. Maps --endpoint-id → id, and applies description.map(String::from).unwrap_or_default() so a missing --description becomes an empty string.
All three sit in the same fault class as service scale (wrong target field, dropped flag, flipped repeatability, wrong default coercion) and are extracted into build_member_update_request, build_invitation_create_request, and build_private_endpoint_create_request helpers under this issue.
crates/clickhousectl/tests/cli_request_shape_test.rs already exists for ClickPipes. That file uses subprocess + wiremock because ClickPipe handlers include runtime behavior that pure struct tests cannot catch, such as reading credential/certificate files and base64 encoding them. This issue deliberately uses a lighter pattern for migrated Cloud commands where the CLI mostly builds typed library request structs.
Fault Classes In Scope
- Request builder miswiring, e.g. assigning
min_replica_memory_gb to max_replica_memory_gb, dropping a flag, flipping a boolean, or choosing the wrong enum variant.
- Clap drift, e.g. a renamed option field silently changing a public
--flag.
Approach
Use plain Rust assertions against library request structs.
No JSON snapshots and no new CLI-layer wiremock tests for these commands. JSON shape and method/path are already covered at the clickhouse-cloud-api boundary.
Concretely:
- Add one minimal and one maximal test per
build_*_request helper in crates/clickhousectl/src/cloud/commands.rs.
- Assert directly on the resulting library struct fields, not serialized JSON.
- Keep existing validation/rejection tests.
- Extract the inline
ServiceReplicaScalingPatchRequest construction in service_scale into a build_service_scale_request helper, and cover it with the same minimal + maximal pattern.
- Cover clap parsing with
Cli::try_parse_from for each body-building command, asserting parsed enum fields.
Commands In Scope
cloud service create
cloud service update
cloud service scale (requires extracting build_service_scale_request)
cloud service reset-password
cloud service query-endpoint create
cloud org update
cloud key create
cloud key update
cloud service backup-config update
cloud member update (requires extracting build_member_update_request)
cloud invitation create (requires extracting build_invitation_create_request)
cloud private-endpoint create (requires extracting build_private_endpoint_create_request)
Out Of Scope
- GET, DELETE, and list commands whose contract is path/query passthrough.
- Full serialized JSON snapshots.
- New CLI-layer wiremock tests for these migrated Cloud commands.
- Broad human-readable output testing, unless a specific non-trivial formatting helper is extracted.
Definition Of Done
- Every in-scope request builder has minimal and maximal struct-field tests.
service scale, member update, invitation create, and private-endpoint create each have a build_*_request helper extracted from inline construction and covered in the same style. These helper extractions are the only production-code changes in this issue.
- Each in-scope command has clap parsing coverage for its body-related flags.
- No new dev-dependencies are introduced by this issue.
cargo test -p clickhousectl passes.
Summary
The CLI now delegates migrated Cloud commands through
clickhouse-cloud-api. The library tests already prove the wire contract: HTTP method, path, auth, and struct → JSON serialization. The remaining useful CLI-specific gap is narrower:clickhouse-cloud-apirequest structs?This issue adds that coverage without adding another CLI-layer wiremock or JSON snapshot suite.
Current State
crates/clickhousectl/src/cloud/commands.rsalready has partial unit coverage for all 8 existingbuild_*_requesthelpers. The gap is not first-time coverage; it is systematic coverage: minimal + maximal inputs with direct assertions on every relevant library struct field.crates/clickhousectl/src/cloud/cli.rsalready has someCli::try_parse_fromtests, but they are selective and mostly prove clap parsing only.service scaledoes not currently use a helper — it constructsServiceReplicaScalingPatchRequestinline inservice_scale(commands.rs:2050-2059). ThreeOption<u32>fields all using.map(f64::from)make this the most accident-prone construction in the file. This issue extracts it into abuild_service_scale_requesthelper for parity and testability.Three other body-building handlers also construct request structs inline and have no unit coverage:
member_update(commands.rs:2492) buildsMemberPatchRequest. Maps the repeatable--role-idVec toassigned_role_ids, collapses an empty Vec toNone, and hardcodesrole: None.invitation_create(commands.rs:2582) buildsInvitationPostRequest. Maps--email→email, repeatable--role-id→assigned_role_ids, usesInvitationPostRequestRole::default().private_endpoint_create(commands.rs:2284) buildsServicPrivateEndpointePostRequest. Maps--endpoint-id→id, and appliesdescription.map(String::from).unwrap_or_default()so a missing--descriptionbecomes an empty string.All three sit in the same fault class as
service scale(wrong target field, dropped flag, flipped repeatability, wrong default coercion) and are extracted intobuild_member_update_request,build_invitation_create_request, andbuild_private_endpoint_create_requesthelpers under this issue.crates/clickhousectl/tests/cli_request_shape_test.rsalready exists for ClickPipes. That file uses subprocess + wiremock because ClickPipe handlers include runtime behavior that pure struct tests cannot catch, such as reading credential/certificate files and base64 encoding them. This issue deliberately uses a lighter pattern for migrated Cloud commands where the CLI mostly builds typed library request structs.Fault Classes In Scope
min_replica_memory_gbtomax_replica_memory_gb, dropping a flag, flipping a boolean, or choosing the wrong enum variant.--flag.Approach
Use plain Rust assertions against library request structs.
No JSON snapshots and no new CLI-layer wiremock tests for these commands. JSON shape and method/path are already covered at the
clickhouse-cloud-apiboundary.Concretely:
build_*_requesthelper incrates/clickhousectl/src/cloud/commands.rs.ServiceReplicaScalingPatchRequestconstruction inservice_scaleinto abuild_service_scale_requesthelper, and cover it with the same minimal + maximal pattern.Cli::try_parse_fromfor each body-building command, asserting parsed enum fields.Commands In Scope
cloud service createcloud service updatecloud service scale(requires extractingbuild_service_scale_request)cloud service reset-passwordcloud service query-endpoint createcloud org updatecloud key createcloud key updatecloud service backup-config updatecloud member update(requires extractingbuild_member_update_request)cloud invitation create(requires extractingbuild_invitation_create_request)cloud private-endpoint create(requires extractingbuild_private_endpoint_create_request)Out Of Scope
Definition Of Done
service scale,member update,invitation create, andprivate-endpoint createeach have abuild_*_requesthelper extracted from inline construction and covered in the same style. These helper extractions are the only production-code changes in this issue.cargo test -p clickhousectlpasses.