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.

Quellinformationen

Repository
cosmian/kms
Letzte Quellaktivität
28. Oktober 2026 um 15:48
Erkannte Sprache von SKILL.md
Englisch
Sterne
367
Forks
43

Installationsoptionen

Standardmäßig ist der Prompt ausgewählt, der zuerst die Quelle prüft. Sie können zu einem direkten Befehl wechseln oder eine lokale Kopie herunterladen.

Quelldateien prüfen

Lesen Sie SKILL.md und alle von SkillsMP angezeigten Begleitdateien, bevor Sie sich für eine Installation entscheiden.

SKILL.md wird angezeigt

SKILL.md
Quellanweisungen · Schreibgeschützte Vorschau
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!( crate::tests::utils::extract_uids::extract_uid(&result, "Unique identifier").is_some(), "expected a unique identifier in the output: {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. let out = my_operation(&conf, &key_id, &["--some-flag", "value"])?; assert!( out.contains("<fragment produced only when --some-flag is set>"), "flag had no visible effect: {out}" ); Ok(()) } ``` Every flag test asserts an effect specific to that flag; a call whose result is discarded proves nothing. ### 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 | Shared singleton KMS: SQLite, plain HTTP, no auth (`auth/plain.toml`) | | `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 | | other server variants | `test_kms_server` crate | See `.github/instructions/rust.instructions.md` § Test KMS servers |
Auf GitHub ansehen