diff options
| author | nsfisis <nsfisis@gmail.com> | 2026-02-21 20:54:29 +0900 |
|---|---|---|
| committer | nsfisis <nsfisis@gmail.com> | 2026-02-21 20:54:29 +0900 |
| commit | e40ae3649d62a933211e81d8ac773fdd86ff1dfb (patch) | |
| tree | 22713882537531c2b0f0a55248045d9f280ce3fe /crates | |
| parent | 3c8ce2b72daccccc88278b8dfbff1a1acc39096c (diff) | |
| download | php-mozart-e40ae3649d62a933211e81d8ac773fdd86ff1dfb.tar.gz php-mozart-e40ae3649d62a933211e81d8ac773fdd86ff1dfb.tar.zst php-mozart-e40ae3649d62a933211e81d8ac773fdd86ff1dfb.zip | |
test(cli): add end-to-end integration tests for CLI commands
Add 23 integration tests using assert_cmd and predicates covering
about, validate, show, licenses, install, config, init, and
dump-autoload commands with shared test helpers and fixture projects.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Diffstat (limited to 'crates')
21 files changed, 698 insertions, 0 deletions
diff --git a/crates/mozart/Cargo.toml b/crates/mozart/Cargo.toml index 287f800..e219fff 100644 --- a/crates/mozart/Cargo.toml +++ b/crates/mozart/Cargo.toml @@ -25,3 +25,5 @@ tokio = { version = "1.49.0", features = ["full"] } zip = { version = "2", default-features = false, features = ["deflate"] } [dev-dependencies] +assert_cmd = "2" +predicates = "3" diff --git a/crates/mozart/tests/cli_about.rs b/crates/mozart/tests/cli_about.rs new file mode 100644 index 0000000..3819d90 --- /dev/null +++ b/crates/mozart/tests/cli_about.rs @@ -0,0 +1,12 @@ +mod common; + +use predicates::str::contains; + +#[test] +fn test_about_prints_version() { + common::mozart_cmd() + .arg("about") + .assert() + .success() + .stdout(contains("Mozart")); +} diff --git a/crates/mozart/tests/cli_config.rs b/crates/mozart/tests/cli_config.rs new file mode 100644 index 0000000..e433653 --- /dev/null +++ b/crates/mozart/tests/cli_config.rs @@ -0,0 +1,40 @@ +mod common; + +use predicates::str::contains; + +#[test] +fn test_config_list() { + let project = common::copy_fixture_to_temp("minimal"); + common::mozart_cmd() + .arg("config") + .arg("--list") + .arg("--working-dir") + .arg(project.path()) + .assert() + .success() + .stdout(contains("vendor-dir")); +} + +#[test] +fn test_config_single_key() { + let project = common::copy_fixture_to_temp("minimal"); + common::mozart_cmd() + .arg("config") + .arg("vendor-dir") + .arg("--working-dir") + .arg(project.path()) + .assert() + .success() + .stdout(contains("vendor")); +} + +#[test] +fn test_config_no_key_fails() { + let project = common::copy_fixture_to_temp("minimal"); + common::mozart_cmd() + .arg("config") + .arg("--working-dir") + .arg(project.path()) + .assert() + .failure(); +} diff --git a/crates/mozart/tests/cli_dump_autoload.rs b/crates/mozart/tests/cli_dump_autoload.rs new file mode 100644 index 0000000..ca592a3 --- /dev/null +++ b/crates/mozart/tests/cli_dump_autoload.rs @@ -0,0 +1,34 @@ +mod common; + +use predicates::str::contains; + +#[test] +fn test_dump_autoload_dry_run() { + let project = common::copy_fixture_to_temp("minimal"); + common::mozart_cmd() + .arg("dump-autoload") + .arg("--dry-run") + .arg("--working-dir") + .arg(project.path()) + .assert() + .success() + .stderr(contains("Dry run")); +} + +#[test] +fn test_dump_autoload_does_not_write_in_dry_run() { + let project = common::copy_fixture_to_temp("minimal"); + let vendor_dir = project.path().join("vendor"); + common::mozart_cmd() + .arg("dump-autoload") + .arg("--dry-run") + .arg("--working-dir") + .arg(project.path()) + .assert() + .success(); + // In dry-run mode, no vendor directory should have been created + assert!( + !vendor_dir.exists(), + "vendor/ should not be created in dry-run mode" + ); +} diff --git a/crates/mozart/tests/cli_init.rs b/crates/mozart/tests/cli_init.rs new file mode 100644 index 0000000..11661fc --- /dev/null +++ b/crates/mozart/tests/cli_init.rs @@ -0,0 +1,25 @@ +mod common; + +#[test] +fn test_init_creates_composer_json() { + let dir = tempfile::TempDir::new().unwrap(); + common::mozart_cmd() + .arg("init") + .arg("--name") + .arg("test/new-project") + .arg("--no-interaction") + .arg("--working-dir") + .arg(dir.path()) + .assert() + .success(); + + assert!( + dir.path().join("composer.json").exists(), + "composer.json should have been created" + ); + + let content = + std::fs::read_to_string(dir.path().join("composer.json")).expect("Failed to read"); + let json: serde_json::Value = serde_json::from_str(&content).expect("Should be valid JSON"); + assert_eq!(json["name"], serde_json::json!("test/new-project")); +} diff --git a/crates/mozart/tests/cli_install.rs b/crates/mozart/tests/cli_install.rs new file mode 100644 index 0000000..5312956 --- /dev/null +++ b/crates/mozart/tests/cli_install.rs @@ -0,0 +1,52 @@ +mod common; + +use predicates::str::contains; + +#[test] +fn test_install_dry_run() { + let project = common::copy_fixture_to_temp("with_lock"); + common::mozart_cmd() + .arg("install") + .arg("--dry-run") + .arg("--working-dir") + .arg(project.path()) + .assert() + .success() + .stderr(contains("Installing")); +} + +#[test] +fn test_install_no_lock_file() { + let project = common::copy_fixture_to_temp("minimal"); + common::mozart_cmd() + .arg("install") + .arg("--working-dir") + .arg(project.path()) + .assert() + .failure() + .stderr(contains("mozart update")); +} + +#[test] +fn test_install_no_composer_json() { + let project = tempfile::TempDir::new().unwrap(); + common::mozart_cmd() + .arg("install") + .arg("--working-dir") + .arg(project.path()) + .assert() + .failure(); +} + +#[test] +fn test_install_dry_run_shows_package_names() { + let project = common::copy_fixture_to_temp("with_lock"); + common::mozart_cmd() + .arg("install") + .arg("--dry-run") + .arg("--working-dir") + .arg(project.path()) + .assert() + .success() + .stderr(contains("psr/log")); +} diff --git a/crates/mozart/tests/cli_licenses.rs b/crates/mozart/tests/cli_licenses.rs new file mode 100644 index 0000000..96d5c4e --- /dev/null +++ b/crates/mozart/tests/cli_licenses.rs @@ -0,0 +1,50 @@ +mod common; + +use predicates::str::contains; + +#[test] +fn test_licenses_locked() { + let project = common::copy_fixture_to_temp("with_lock"); + common::mozart_cmd() + .arg("licenses") + .arg("--locked") + .arg("--working-dir") + .arg(project.path()) + .assert() + .success() + .stdout(contains("MIT")); +} + +#[test] +fn test_licenses_format_json() { + let project = common::copy_fixture_to_temp("with_lock"); + let output = common::mozart_cmd() + .arg("licenses") + .arg("--format=json") + .arg("--locked") + .arg("--working-dir") + .arg(project.path()) + .assert() + .success() + .get_output() + .stdout + .clone(); + + let stdout = String::from_utf8_lossy(&output); + serde_json::from_str::<serde_json::Value>(&stdout) + .expect("licenses --format=json output should be valid JSON"); +} + +#[test] +fn test_licenses_format_summary() { + let project = common::copy_fixture_to_temp("with_lock"); + common::mozart_cmd() + .arg("licenses") + .arg("--format=summary") + .arg("--locked") + .arg("--working-dir") + .arg(project.path()) + .assert() + .success() + .stdout(contains("MIT")); +} diff --git a/crates/mozart/tests/cli_show.rs b/crates/mozart/tests/cli_show.rs new file mode 100644 index 0000000..0ecb588 --- /dev/null +++ b/crates/mozart/tests/cli_show.rs @@ -0,0 +1,65 @@ +mod common; + +use predicates::str::contains; + +#[test] +fn test_show_locked_packages() { + let project = common::copy_fixture_to_temp("with_lock"); + common::mozart_cmd() + .arg("show") + .arg("--locked") + .arg("--working-dir") + .arg(project.path()) + .assert() + .success() + .stdout(contains("psr/log")); +} + +#[test] +fn test_show_self() { + let project = common::copy_fixture_to_temp("with_lock"); + common::mozart_cmd() + .arg("show") + .arg("--self-info") + .arg("--working-dir") + .arg(project.path()) + .assert() + .success() + .stdout(contains("test/locked-project")); +} + +#[test] +fn test_show_name_only() { + let project = common::copy_fixture_to_temp("with_lock"); + common::mozart_cmd() + .arg("show") + .arg("--name-only") + .arg("--locked") + .arg("--working-dir") + .arg(project.path()) + .assert() + .success() + .stdout(contains("psr/log")); +} + +#[test] +fn test_show_format_json() { + let project = common::copy_fixture_to_temp("with_lock"); + let output = common::mozart_cmd() + .arg("show") + .arg("--format=json") + .arg("--locked") + .arg("--working-dir") + .arg(project.path()) + .assert() + .success() + .get_output() + .stdout + .clone(); + + let stdout = String::from_utf8_lossy(&output); + // Output should be parseable JSON + let parsed: serde_json::Value = + serde_json::from_str(&stdout).expect("show --format=json output should be valid JSON"); + assert!(parsed.is_array() || parsed.is_object()); +} diff --git a/crates/mozart/tests/cli_validate.rs b/crates/mozart/tests/cli_validate.rs new file mode 100644 index 0000000..fe3c080 --- /dev/null +++ b/crates/mozart/tests/cli_validate.rs @@ -0,0 +1,60 @@ +mod common; + +#[test] +fn test_validate_valid_project() { + let project = common::copy_fixture_to_temp("minimal"); + common::mozart_cmd() + .arg("validate") + .arg("--working-dir") + .arg(project.path()) + .assert() + .success(); +} + +#[test] +fn test_validate_valid_with_lock() { + let project = common::copy_fixture_to_temp("with_lock"); + common::mozart_cmd() + .arg("validate") + .arg("--working-dir") + .arg(project.path()) + .assert() + .success(); +} + +#[test] +fn test_validate_invalid_json() { + let project = common::copy_fixture_to_temp("invalid_json"); + common::mozart_cmd() + .arg("validate") + .arg("--working-dir") + .arg(project.path()) + .assert() + .code(2); +} + +#[test] +fn test_validate_missing_composer_json() { + let project = tempfile::TempDir::new().unwrap(); + common::mozart_cmd() + .arg("validate") + .arg("--working-dir") + .arg(project.path()) + .assert() + .failure(); +} + +#[test] +fn test_validate_strict_with_warnings() { + // A composer.json without a license generates a warning. + // With --strict, warnings become a non-zero exit code. + let project = + common::setup_temp_project(r#"{"name": "test/no-license", "require": {"php": ">=8.1"}}"#); + common::mozart_cmd() + .arg("validate") + .arg("--strict") + .arg("--working-dir") + .arg(project.path()) + .assert() + .code(1); +} diff --git a/crates/mozart/tests/common/mod.rs b/crates/mozart/tests/common/mod.rs new file mode 100644 index 0000000..bec4677 --- /dev/null +++ b/crates/mozart/tests/common/mod.rs @@ -0,0 +1,74 @@ +use std::fs; +use std::path::{Path, PathBuf}; +use tempfile::TempDir; + +/// Returns a `Command` configured to run the `mozart` binary. +pub fn mozart_cmd() -> assert_cmd::Command { + assert_cmd::cargo::cargo_bin_cmd!("mozart") +} + +/// Returns the absolute path to `tests/fixtures/<name>`. +pub fn fixture_dir(name: &str) -> PathBuf { + Path::new(env!("CARGO_MANIFEST_DIR")) + .join("tests") + .join("fixtures") + .join(name) +} + +/// A temporary project directory that is cleaned up when dropped. +pub struct TestProject { + #[allow(dead_code)] + pub dir: TempDir, +} + +impl TestProject { + /// Returns the path to the temp directory root. + #[allow(dead_code)] + pub fn path(&self) -> &Path { + self.dir.path() + } +} + +/// Create a temporary project with just a `composer.json`. +#[allow(dead_code)] +pub fn setup_temp_project(composer_json: &str) -> TestProject { + let dir = TempDir::new().expect("Failed to create temp dir"); + fs::write(dir.path().join("composer.json"), composer_json) + .expect("Failed to write composer.json"); + TestProject { dir } +} + +/// Create a temporary project with both `composer.json` and `composer.lock`. +#[allow(dead_code)] +pub fn setup_temp_project_with_lock(composer_json: &str, composer_lock: &str) -> TestProject { + let dir = TempDir::new().expect("Failed to create temp dir"); + fs::write(dir.path().join("composer.json"), composer_json) + .expect("Failed to write composer.json"); + fs::write(dir.path().join("composer.lock"), composer_lock) + .expect("Failed to write composer.lock"); + TestProject { dir } +} + +/// Copy an entire fixture directory to a new temp directory. +#[allow(dead_code)] +pub fn copy_fixture_to_temp(fixture_name: &str) -> TestProject { + let src = fixture_dir(fixture_name); + let dir = TempDir::new().expect("Failed to create temp dir"); + copy_dir_recursive(&src, dir.path()).expect("Failed to copy fixture"); + TestProject { dir } +} + +fn copy_dir_recursive(src: &Path, dst: &Path) -> anyhow::Result<()> { + for entry in fs::read_dir(src)? { + let entry = entry?; + let file_type = entry.file_type()?; + let dst_path = dst.join(entry.file_name()); + if file_type.is_dir() { + fs::create_dir_all(&dst_path)?; + copy_dir_recursive(&entry.path(), &dst_path)?; + } else { + fs::copy(entry.path(), dst_path)?; + } + } + Ok(()) +} diff --git a/crates/mozart/tests/fixtures/invalid_json/composer.json b/crates/mozart/tests/fixtures/invalid_json/composer.json new file mode 100644 index 0000000..e367425 --- /dev/null +++ b/crates/mozart/tests/fixtures/invalid_json/composer.json @@ -0,0 +1 @@ +{this is not valid json diff --git a/crates/mozart/tests/fixtures/minimal/composer.json b/crates/mozart/tests/fixtures/minimal/composer.json new file mode 100644 index 0000000..a84ee9f --- /dev/null +++ b/crates/mozart/tests/fixtures/minimal/composer.json @@ -0,0 +1,8 @@ +{ + "name": "test/minimal-project", + "description": "A minimal test project", + "license": "MIT", + "require": { + "php": ">=8.1" + } +} diff --git a/crates/mozart/tests/fixtures/with_dev_deps/composer.json b/crates/mozart/tests/fixtures/with_dev_deps/composer.json new file mode 100644 index 0000000..09126f2 --- /dev/null +++ b/crates/mozart/tests/fixtures/with_dev_deps/composer.json @@ -0,0 +1,12 @@ +{ + "name": "test/dev-project", + "description": "A project with dev dependencies", + "license": "MIT", + "require": { + "php": ">=8.1", + "psr/log": "^3.0" + }, + "require-dev": { + "phpunit/phpunit": "^11.0" + } +} diff --git a/crates/mozart/tests/fixtures/with_dev_deps/composer.lock b/crates/mozart/tests/fixtures/with_dev_deps/composer.lock new file mode 100644 index 0000000..d01e341 --- /dev/null +++ b/crates/mozart/tests/fixtures/with_dev_deps/composer.lock @@ -0,0 +1,80 @@ +{ + "_readme": [ + "This file locks the dependencies of your project to a known state", + "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", + "This file is @generated automatically" + ], + "content-hash": "ed17fac2f30232d69c31587151006a2f", + "packages": [ + { + "name": "psr/log", + "version": "3.0.2", + "source": { + "type": "git", + "url": "https://github.com/php-fig/log.git", + "reference": "f16e1d5863e37f8d8c2a01719f5b34baa2b714d3" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/log/zipball/f16e1d5863e37f8d8c2a01719f5b34baa2b714d3", + "reference": "f16e1d5863e37f8d8c2a01719f5b34baa2b714d3", + "shasum": "" + }, + "require": { + "php": ">=8.0.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Psr\\Log\\": "src" + } + }, + "license": [ + "MIT" + ], + "description": "Common interface for logging libraries", + "time": "2024-09-11T13:17:53+00:00" + } + ], + "packages-dev": [ + { + "name": "phpunit/phpunit", + "version": "11.0.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/phpunit.git", + "reference": "fake000000000000000000000000000000000001" + }, + "dist": { + "type": "zip", + "url": "https://fake.example.com/phpunit-11.0.0.zip", + "reference": "fake000000000000000000000000000000000001", + "shasum": "" + }, + "require": { + "php": ">=8.2" + }, + "type": "library", + "autoload": { + "psr-4": { + "PHPUnit\\": "src/" + } + }, + "license": [ + "BSD-3-Clause" + ], + "description": "The PHP Unit Testing framework", + "time": "2024-02-02T06:38:00+00:00" + } + ], + "aliases": [], + "minimum-stability": "stable", + "stability-flags": {}, + "prefer-stable": false, + "prefer-lowest": false, + "platform": { + "php": ">=8.1" + }, + "platform-dev": {}, + "plugin-api-version": "2.6.0" +} diff --git a/crates/mozart/tests/fixtures/with_dev_deps/vendor/composer/installed.json b/crates/mozart/tests/fixtures/with_dev_deps/vendor/composer/installed.json new file mode 100644 index 0000000..efa3905 --- /dev/null +++ b/crates/mozart/tests/fixtures/with_dev_deps/vendor/composer/installed.json @@ -0,0 +1,32 @@ +{ + "packages": [ + { + "name": "phpunit/phpunit", + "version": "11.0.0", + "version_normalized": "11.0.0.0", + "type": "library", + "install-path": "../phpunit/phpunit", + "autoload": { + "psr-4": { + "PHPUnit\\": "src/" + } + } + }, + { + "name": "psr/log", + "version": "3.0.2", + "version_normalized": "3.0.2.0", + "type": "library", + "install-path": "../psr/log", + "autoload": { + "psr-4": { + "Psr\\Log\\": "src" + } + } + } + ], + "dev-package-names": [ + "phpunit/phpunit" + ], + "dev": true +} diff --git a/crates/mozart/tests/fixtures/with_installed/composer.json b/crates/mozart/tests/fixtures/with_installed/composer.json new file mode 100644 index 0000000..1653318 --- /dev/null +++ b/crates/mozart/tests/fixtures/with_installed/composer.json @@ -0,0 +1,9 @@ +{ + "name": "test/locked-project", + "description": "A project with a lock file", + "license": "MIT", + "require": { + "php": ">=8.1", + "psr/log": "^3.0" + } +} diff --git a/crates/mozart/tests/fixtures/with_installed/composer.lock b/crates/mozart/tests/fixtures/with_installed/composer.lock new file mode 100644 index 0000000..0e4f7aa --- /dev/null +++ b/crates/mozart/tests/fixtures/with_installed/composer.lock @@ -0,0 +1,56 @@ +{ + "_readme": [ + "This file locks the dependencies of your project to a known state", + "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", + "This file is @generated automatically" + ], + "content-hash": "ca27a8c1835db5af5fd5efe936f402b4", + "packages": [ + { + "name": "psr/log", + "version": "3.0.2", + "source": { + "type": "git", + "url": "https://github.com/php-fig/log.git", + "reference": "f16e1d5863e37f8d8c2a01719f5b34baa2b714d3" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/log/zipball/f16e1d5863e37f8d8c2a01719f5b34baa2b714d3", + "reference": "f16e1d5863e37f8d8c2a01719f5b34baa2b714d3", + "shasum": "" + }, + "require": { + "php": ">=8.0.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Psr\\Log\\": "src" + } + }, + "license": [ + "MIT" + ], + "description": "Common interface for logging libraries", + "homepage": "https://www.php-fig.org/", + "keywords": [ + "log", + "psr", + "psr-3" + ], + "time": "2024-09-11T13:17:53+00:00" + } + ], + "packages-dev": [], + "aliases": [], + "minimum-stability": "stable", + "stability-flags": {}, + "prefer-stable": false, + "prefer-lowest": false, + "platform": { + "php": ">=8.1" + }, + "platform-dev": {}, + "plugin-api-version": "2.6.0" +} diff --git a/crates/mozart/tests/fixtures/with_installed/vendor/composer/installed.json b/crates/mozart/tests/fixtures/with_installed/vendor/composer/installed.json new file mode 100644 index 0000000..bbbe53c --- /dev/null +++ b/crates/mozart/tests/fixtures/with_installed/vendor/composer/installed.json @@ -0,0 +1,18 @@ +{ + "packages": [ + { + "name": "psr/log", + "version": "3.0.2", + "version_normalized": "3.0.2.0", + "type": "library", + "install-path": "../psr/log", + "autoload": { + "psr-4": { + "Psr\\Log\\": "src" + } + } + } + ], + "dev-package-names": [], + "dev": true +} diff --git a/crates/mozart/tests/fixtures/with_lock/composer.json b/crates/mozart/tests/fixtures/with_lock/composer.json new file mode 100644 index 0000000..1653318 --- /dev/null +++ b/crates/mozart/tests/fixtures/with_lock/composer.json @@ -0,0 +1,9 @@ +{ + "name": "test/locked-project", + "description": "A project with a lock file", + "license": "MIT", + "require": { + "php": ">=8.1", + "psr/log": "^3.0" + } +} diff --git a/crates/mozart/tests/fixtures/with_lock/composer.lock b/crates/mozart/tests/fixtures/with_lock/composer.lock new file mode 100644 index 0000000..0e4f7aa --- /dev/null +++ b/crates/mozart/tests/fixtures/with_lock/composer.lock @@ -0,0 +1,56 @@ +{ + "_readme": [ + "This file locks the dependencies of your project to a known state", + "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", + "This file is @generated automatically" + ], + "content-hash": "ca27a8c1835db5af5fd5efe936f402b4", + "packages": [ + { + "name": "psr/log", + "version": "3.0.2", + "source": { + "type": "git", + "url": "https://github.com/php-fig/log.git", + "reference": "f16e1d5863e37f8d8c2a01719f5b34baa2b714d3" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/log/zipball/f16e1d5863e37f8d8c2a01719f5b34baa2b714d3", + "reference": "f16e1d5863e37f8d8c2a01719f5b34baa2b714d3", + "shasum": "" + }, + "require": { + "php": ">=8.0.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Psr\\Log\\": "src" + } + }, + "license": [ + "MIT" + ], + "description": "Common interface for logging libraries", + "homepage": "https://www.php-fig.org/", + "keywords": [ + "log", + "psr", + "psr-3" + ], + "time": "2024-09-11T13:17:53+00:00" + } + ], + "packages-dev": [], + "aliases": [], + "minimum-stability": "stable", + "stability-flags": {}, + "prefer-stable": false, + "prefer-lowest": false, + "platform": { + "php": ">=8.1" + }, + "platform-dev": {}, + "plugin-api-version": "2.6.0" +} diff --git a/crates/mozart/tests/hash_helper.rs b/crates/mozart/tests/hash_helper.rs new file mode 100644 index 0000000..301c72c --- /dev/null +++ b/crates/mozart/tests/hash_helper.rs @@ -0,0 +1,3 @@ +// This file is intentionally empty. +// It was a temporary helper used during development. +// The actual integration tests are in cli_*.rs files. |
