Skip to main content

ckms-subcommand-test

Generate and run integration tests for a new or modified ckms subcommand: test file, helpers, happy-path + error-case + CO-gating coverage, module registration, cargo test -p ckms. Use after adding a ckms subcommand or flag, or when CI/review flags missing CLI test coverage.

Source facts

Repository
cosmian/kms
Last source activity
October 15, 2026 at 14:44
Detected SKILL.md language
English
Stars
362
Forks
41

Install options

The review-first prompt is selected by default. You can switch to a direct command or download a local copy.

Review the source files

Read SKILL.md and any companion files shown by SkillsMP before deciding whether to install.

Showing SKILL.md

SKILL.md
Source instructions · Read-only preview
name
ckms-subcommand-test
description
Generate and run integration tests for a new or modified ckms subcommand: test file, helpers, happy-path + error-case + CO-gating coverage, module registration, cargo test -p ckms. Use after adding a ckms subcommand or flag, or when CI/review flags missing CLI test coverage.
# ckms Subcommand Integration Tests Write and run integration tests for a new or modified `ckms` subcommand, following the project's established test patterns. ## When to Use - After adding a new `ckms` subcommand or new flags to an existing one. - When a code review or CI failure indicates missing test coverage for a CLI command. - When `/kms-sync-rules` emits sync rule 4.4 (CLI ↔ UI parity) and the CLI side has no tests. --- ## Step 0 — Identify the subcommand Locate the implementation: ```bash find crate/clients/clap/src/actions -name "*.rs" | xargs grep -l "struct.*Args\|#\[command\]" | sort ``` Read the clap struct to enumerate: - The subcommand path (e.g. `sym keys create-split-key`) - Every flag and its type, default value, required vs optional - Feature gates: `#[cfg(feature = "non-fips")]` on the struct or its module Also check the HTTP client wrapper in `crate/clients/client/src/kms_rest_client.rs` to understand what the action calls server-side. --- ## Step 1 — Choose or create the test directory The convention is one directory per CLI feature area: ``` crate/clients/ckms/src/tests/ <feature>/ ← e.g. split_key/, rsa/, ec/ mod.rs <operation>.rs ← e.g. create_split_key.rs, join_split_key.rs ``` Check whether a directory for this feature area already exists: ```bash ls crate/clients/ckms/src/tests/ ``` - **Existing directory** → add a new file (or add tests to an existing file). - **New feature area** → create the directory, `mod.rs`, and the first test file. --- ## Step 2 — Write the test file ### 2a. Imports ```rust use test_kms_server::start_default_test_kms_server; // adjust variant as needed use crate::{ error::result::CosmianResult, tests::utils::{owner_config, run_ckms, run_ckms_expect_error}, }; ``` For CO-gated commands: ```rust use test_kms_server::start_default_test_kms_server_with_crypto_officer_users; use crate::tests::utils::load_client_config; ``` ### 2b. Public helper function Every operation file exposes a `pub(crate)` helper function so other test files can compose it without duplicating the CLI call: ```rust /// Create a split-key from `source_uid` into `n` shares. /// /// `extra_args` are appended after the base subcommand args. pub(crate) fn create_split_key( cli_conf_path: &str, source_uid: &str, n: u32, extra_args: &[&str], ) -> CosmianResult<Vec<String>> { let n_str = n.to_string(); let mut args = vec!["sym", "keys", "create-split-key", "--key-id", source_uid, "--split-key-parts", &n_str]; args.extend_from_slice(extra_args); let stdout = run_ckms(cli_conf_path, &args)?; // Parse UIDs from stdout — adapt to actual output format. Ok(stdout.lines().filter_map(|l| { crate::tests::utils::extract_uids::extract_uid(l, "Unique identifier") .map(str::to_owned) }).collect()) } ``` ### 2c. Happy path test(s) ```rust #[tokio::test] async fn test_<operation>_default() -> CosmianResult<()> { let ctx = start_default_test_kms_server().await; let conf = owner_config(ctx); // Setup: create prerequisite objects. let key_id = crate::tests::symmetric::create_key::create_symmetric_key(&conf, &[])?; // Exercise the subcommand. let result = my_operation(&conf, &key_id, &[])?; // Assert invariants. assert!(!result.is_empty(), "expected non-empty result"); Ok(()) } ``` ### 2d. Flag coverage test(s) One test per non-trivial flag combination that exercises different code paths: ```rust #[tokio::test] async fn test_<operation>_with_flags() -> CosmianResult<()> { let ctx = start_default_test_kms_server().await; let conf = owner_config(ctx); let key_id = crate::tests::symmetric::create_key::create_symmetric_key(&conf, &[])?; // Exercise specific flag. my_operation(&conf, &key_id, &["--some-flag", "value"])?; Ok(()) } ``` ### 2e. Error path test(s) At minimum: one test that expects the CLI to fail (wrong input, missing permission, etc.): ```rust #[tokio::test] async fn test_<operation>_invalid_input_returns_error() -> CosmianResult<()> { let ctx = start_default_test_kms_server().await; let conf = owner_config(ctx); let stderr = run_ckms_expect_error(&conf, &["sym", "keys", "my-op", "--invalid"])?; assert!( stderr.contains("expected fragment"), "unexpected error: {stderr}" ); Ok(()) } ``` ### 2f. CO-gated tests When the command requires `crypto_officer_users`: ```rust #[cfg(feature = "non-fips")] #[tokio::test] async fn test_<operation>_requires_co_role() -> CosmianResult<()> { // Operator (not listed in crypto_officer_users) must be rejected. let ctx = start_default_test_kms_server_with_crypto_officer_users( vec!["owner.client.acme.com".to_owned()] ).await; let operator_conf = load_client_config("kmserver.client.acme.com.toml", ctx); let stderr = run_ckms_expect_error(&operator_conf, &["sym", "keys", "my-op", ...])?; assert!(stderr.contains("Unauthorized") || stderr.contains("permission"), "expected auth error, got: {stderr}"); Ok(()) } ``` --- ## Step 3 — Register the module ### New file in existing directory Add to the directory's `mod.rs`: ```rust mod my_new_file; pub(crate) use my_new_file::my_helper_fn; // re-export if needed by other modules ``` ### New directory 1. Create `crate/clients/ckms/src/tests/<feature>/mod.rs` declaring submodules. 2. Add to `crate/clients/ckms/src/tests/mod.rs`: ```rust // add feature gate if non-fips-only: #[cfg(feature = "non-fips")] mod <feature>; ``` --- ## Step 4 — Run the new tests ```bash # Run only the new tests (fast feedback): cargo test -p ckms -- <feature> # Run the full ckms suite to check for regressions: cargo test -p ckms ``` Fix every failure before proceeding. Never mark tests `#[ignore]`. --- ## Step 5 — Coverage checklist Verify before closing the task: - [ ] Happy path: default flags succeed and produce a parseable output. - [ ] Every non-default flag exercised at least once. - [ ] At least one error case: invalid input, object not found, or permission denied. - [ ] CO-gated commands: operator rejection test exists (`#[cfg(feature = "non-fips")]`). - [ ] Helper function is `pub(crate)` and re-exported from `mod.rs` if other tests need it. - [ ] New module registered in `crate/clients/ckms/src/tests/mod.rs` with correct feature gate. - [ ] `cargo test -p ckms -- <feature>` passes with 0 failures. --- ## Reference: test utilities | Helper | Location | Purpose | |--------|----------|---------| | `run_ckms(conf, args)` | `tests/utils/run.rs` | Run ckms, return stdout or propagate stderr as error | | `run_ckms_expect_error(conf, args)` | `tests/utils/run.rs` | Run ckms, return stderr; fail if command succeeds | | `ckms_bin()` | `tests/utils/run.rs` | Raw `Command` for ckms (use when building multi-step commands) | | `recover_cmd_logs(cmd)` | `tests/utils/cmd_logs.rs` | Run a `Command`, capture stdout+stderr | | `owner_config(ctx)` | `tests/utils/mod.rs` | Path to `owner.client.acme.com.toml` config | | `load_client_config(name, ctx)` | `tests/utils/mod.rs` | Path to any test-cert config file | | `extract_uid(stdout, label)` | `tests/utils/extract_uids.rs` | Parse a UID from a line like `Unique identifier: <uid>` | | `start_default_test_kms_server()` | `test_kms_server` crate | Start an in-process KMS with SQLite + cert auth | | `start_default_test_kms_server_with_crypto_officer_users(users)` | `test_kms_server` crate | Start KMS with CO role enabled | | `start_default_test_kms_server_with_multi_crypto_officer_users()` | `test_kms_server` crate | Two-CO pre-configured setup |
View on GitHub