Skip to main content

write-sdk-integration-test

Guide for writing SDK integration tests using the module-per-command structure in packages/sdk/tests/local_operations/, covering shared helpers, the it_should_* naming convention, test isolation, and module registration. Triggers on "sdk integration test", "write sdk test", "test sdk method", "add sdk test", "integration test for sdk", "test deployer method", or "sdk test module".

Quellinformationen

Repository
torrust/torrust-tracker-deployer
Letzte Quellaktivität
25. Februar 2026 um 19:30
Erkannte Sprache von SKILL.md
Englisch
Sterne
4
Forks
2

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
write-sdk-integration-test
description
Guide for writing SDK integration tests using the module-per-command structure in packages/sdk/tests/local_operations/, covering shared helpers, the it_should_* naming convention, test isolation, and module registration. Triggers on "sdk integration test", "write sdk test", "test sdk method", "add sdk test", "integration test for sdk", "test deployer method", or "sdk test module".
metadata
{"author":"torrust","version":"1.0"}
# Write an SDK Integration Test SDK integration tests live in `packages/sdk/tests/local_operations/` and are the only tests written entirely against the **public** SDK API — with no access to internal implementation details. They exercise the SDK exactly as an external consumer would. No infrastructure (LXD, Docker, SSH) is required for these tests; they only cover local operations. --- ## Quick Decision Tree - Adding tests for a **new method**? → Follow the full guide below to create a new module file and register it. - Adding an **extra test case** to an existing method? → Skip to [Phase 3 — Write the Test Function](#phase-3-write-the-test-function). --- ## Directory Structure ```text packages/sdk/tests/ └── local_operations/ ├── main.rs ← entry point: shared helpers + `mod` declarations ├── builder.rs ← DeployerBuilder error cases ├── create.rs ← create_environment (typed + JSON file) ├── destroy.rs ← destroy ├── exists.rs ← exists before/after create ├── list.rs ← list (populated + empty) ├── purge.rs ← purge ├── show.rs ← show + not-found ├── validate.rs ← validate (valid + invalid config) └── workflow.rs ← chained operations end-to-end ``` One module per SDK command. Mirror this structure when adding a new command. --- ## Phase 1: Create the Module File Create `packages/sdk/tests/local_operations/{command}.rs`. Use the file module template below; replace `my_command` with the command name and write real test cases instead of the placeholder. ```rust // packages/sdk/tests/local_operations/my_command.rs use crate::{create_environment, deployer_in_temp_dir, minimal_config}; #[test] fn it_should_succeed_when_environment_exists() { // Arrange let (deployer, _tmp) = deployer_in_temp_dir(); let name = create_environment(&deployer, "my-cmd-test"); // Act let result = deployer.my_command(&name); // Assert assert!(result.is_ok(), "my_command failed: {:?}", result.err()); } #[test] fn it_should_return_error_when_environment_does_not_exist() { // Arrange let (deployer, _tmp) = deployer_in_temp_dir(); let name = minimal_config("nonexistent").name; // Act let result = deployer.my_command(&name); // Assert assert!(result.is_err(), "expected error for unknown environment"); } ``` ### Naming convention All test functions follow the pattern: ```text it_should_{behavior}_when_{condition} ``` Examples: - `it_should_return_empty_list_when_no_environments_exist` - `it_should_return_error_when_environment_not_found` - `it_should_succeed_when_environment_was_previously_created` --- ## Phase 2: Register the Module in `main.rs` Add a `mod` declaration at the top of `packages/sdk/tests/local_operations/main.rs` alongside the other module declarations: ```rust // packages/sdk/tests/local_operations/main.rs mod builder; mod create; mod destroy; mod exists; mod list; mod my_command; // ← add this line, in alphabetical order mod purge; mod show; mod validate; mod workflow; ``` --- ## Phase 3: Write the Test Function ### Shared helpers (from `main.rs` — import via `crate::`) All helpers are `pub(crate)` and resolved through the `crate::` prefix inside any module file. | Helper | Purpose | | -------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------- | | `deployer_in_temp_dir() -> (Deployer, TempDir)` | Fresh isolated workspace per test. **Always bind `_tmp`** — dropping it deletes the dir. | | `minimal_config(name: &str) -> EnvironmentCreationConfig` | Minimal valid config using `fixtures/` SSH keys. | | `create_environment(deployer: &Deployer, name: &str) -> EnvironmentName` | Create environment and unwrap; panics with context on failure. | | `write_config_json(dir: &Path, filename: &str, env_name: &str) -> PathBuf` | Write a minimal valid JSON config to disk. | | `fixture_ssh_keys() -> (PathBuf, PathBuf)` | Absolute paths to `fixtures/testing_rsa` keypair. | | `repo_root() -> PathBuf` | Absolute path to the repository root. | | `assert_environment_exists(deployer, name)` | Asserts `deployer.exists(name)` is `true`. | | `assert_environment_not_exists(deployer, name)` | Asserts `deployer.exists(name)` is `false`. | ### Isolation pattern Each test must get its own `deployer_in_temp_dir()`. Tests **must not** share state or depend on the execution order of other tests. ```rust #[test] fn it_should_return_true_when_environment_was_created() { // Each test gets a fresh, independent workspace let (deployer, _tmp) = deployer_in_temp_dir(); let name = create_environment(&deployer, "exists-test"); assert!(deployer.exists(&name).unwrap()); } ``` ### Error-path tests Import error types from the public SDK surface only: ```rust use torrust_tracker_deployer_sdk::CreateCommandHandlerError; #[test] fn it_should_return_already_exists_when_created_twice() { let (deployer, _tmp) = deployer_in_temp_dir(); let config = crate::minimal_config("dup"); deployer.create_environment(config.clone()).unwrap(); let result = deployer.create_environment(config); assert!( matches!(result, Err(CreateCommandHandlerError::AlreadyExists(_))), "expected AlreadyExists, got: {result:?}" ); } ``` ### Workflow / multi-step tests For tests that chain multiple operations, place them in `workflow.rs`: ```rust #[test] fn it_should_allow_full_local_lifecycle() { let (deployer, _tmp) = deployer_in_temp_dir(); // create let name = create_environment(&deployer, "lifecycle"); assert_environment_exists(&deployer, &name); // list let list = deployer.list().unwrap(); assert_eq!(list.len(), 1); // show let info = deployer.show(&name).unwrap(); assert_eq!(info.name, name); // destroy deployer.destroy(&name).unwrap(); assert_environment_not_exists(&deployer, &name); } ``` --- ## Phase 4: Run the Tests ```bash # All SDK tests cargo test -p torrust-tracker-deployer-sdk # Only the new module cargo test -p torrust-tracker-deployer-sdk my_command ``` Expected: the new tests appear in the output and pass. --- ## Phase 5: Run All Checks ```bash cargo run --bin linter all ``` Fix any linting issues (unused imports, missing doc-comments on public items) before committing. --- ## Commit Follow the commit conventions in `.github/skills/dev/git-workflow/commit-changes/skill.md`. Typical subject line: ```text test(sdk): add integration tests for my_command ``` Group in a single commit: - `packages/sdk/tests/local_operations/my_command.rs` (new file) - `packages/sdk/tests/local_operations/main.rs` (module declaration)
Auf GitHub ansehen