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.

Informations de source

Dépôt
cosmian/kms
Dernière activité de la source
28 octobre 2026 à 15:48
Langue détectée de SKILL.md
anglais
Étoiles
367
Forks
43

Options d'installation

Le prompt qui vérifie d'abord la source est sélectionné par défaut. Vous pouvez passer à une commande directe ou télécharger une copie locale.

Vérifiez les fichiers source

Lisez SKILL.md et les fichiers associés affichés par SkillsMP avant de décider de l'installer.

Affichage de SKILL.md

SKILL.md
Instructions source · Aperçu en lecture seule
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 |
Voir sur GitHub