diff options
| author | nsfisis <nsfisis@gmail.com> | 2026-06-26 02:23:58 +0900 |
|---|---|---|
| committer | nsfisis <nsfisis@gmail.com> | 2026-06-26 02:23:58 +0900 |
| commit | 6c89c720d9e4728c288e3c66ffb7dcb4cba5121c (patch) | |
| tree | 4697f897302425df1b57eb87bf9441308530d0bf /crates/shirabe/tests/command/remove_command_test.rs | |
| parent | 4bd544608e4934fff4463fd6b104d06162a51ba0 (diff) | |
| download | php-shirabe-6c89c720d9e4728c288e3c66ffb7dcb4cba5121c.tar.gz php-shirabe-6c89c720d9e4728c288e3c66ffb7dcb4cba5121c.tar.zst php-shirabe-6c89c720d9e4728c288e3c66ffb7dcb4cba5121c.zip | |
test(command): port show/remove/global/audit/check-platform-reqs/status/self-update/validate tests
Faithfully port the remaining stubbed command test bodies from their PHP
counterparts (expected values verbatim). Newly passing: remove (6), global (2),
check-platform-reqs (1), status (1), audit (1), self-update (1).
Tests whose ported bodies reach a genuine unported src path keep faithful bodies
but stay #[ignore] with precise reasons. Dominant blockers surfaced:
- ShowCommand::configure stub (empty set_definition) blocks all 42 show tests
- Composer-handle RefCell re-entrancy in the Installer (composer.rs:500) and
Factory::create_composer (composer.rs:446) paths
- check_platform_reqs nests InstalledRepository in InstalledRepository
- audit's non-locked branch (audit_command.rs:285) and write_error3 re-entrancy
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Diffstat (limited to 'crates/shirabe/tests/command/remove_command_test.rs')
| -rw-r--r-- | crates/shirabe/tests/command/remove_command_test.rs | 957 |
1 files changed, 925 insertions, 32 deletions
diff --git a/crates/shirabe/tests/command/remove_command_test.rs b/crates/shirabe/tests/command/remove_command_test.rs index 2ea8c88..3fc125b 100644 --- a/crates/shirabe/tests/command/remove_command_test.rs +++ b/crates/shirabe/tests/command/remove_command_test.rs @@ -1,97 +1,990 @@ //! ref: composer/tests/Composer/Test/Command/RemoveCommandTest.php -#[ignore = "missing TestCase::get_application_tester (ApplicationTester) infrastructure"] +use crate::test_case::{ + RunOptions, create_composer_lock, create_installed_json, get_application_tester, get_package, + init_temp_composer, +}; +use indexmap::IndexMap; +use serial_test::serial; +use shirabe::json::JsonFile; +use shirabe::package::Link; +use shirabe::package::handle::PackageInterfaceHandle; +use shirabe_php_shim::PhpMixed; +use shirabe_semver::constraint::{AnyConstraint, MatchAllConstraint}; + +// Symfony\Component\Console\Command\Command exit codes. +const SUCCESS: i32 = 0; +const FAILURE: i32 = 1; +const INVALID: i32 = 2; + +fn input(pairs: Vec<(&str, PhpMixed)>) -> Vec<(PhpMixed, PhpMixed)> { + pairs + .into_iter() + .map(|(k, v)| (PhpMixed::from(k), v)) + .collect() +} + +/// Read a JSON file in the CWD and decode it to a `serde_json::Value`. +fn read_json_file(path: &str) -> serde_json::Value { + let mut json = JsonFile::new(path.to_string(), None, None).unwrap(); + let read = json.read().unwrap(); + serde_json::from_str(&JsonFile::encode(&read)).unwrap() +} + +/// Mirrors PHPUnit's assertEmpty for a decoded JSON document (empty object/array/null). +fn is_empty_json(value: &serde_json::Value) -> bool { + match value { + serde_json::Value::Null => true, + serde_json::Value::Object(map) => map.is_empty(), + serde_json::Value::Array(list) => list.is_empty(), + _ => false, + } +} + #[test] +#[serial] fn test_exception_running_with_no_remove_packages() { - todo!() + let mut app_tester = get_application_tester(); + let err = app_tester + .run( + input(vec![("command", PhpMixed::from("remove"))]), + RunOptions::default(), + ) + .expect_err("expected InvalidArgumentException for missing packages argument"); + assert!( + err.to_string() + .contains(r#"Not enough arguments (missing: "packages")."#), + "got: {}", + err + ); } -#[ignore = "missing TestCase::init_temp_composer and get_application_tester (ApplicationTester) infrastructure"] #[test] +#[serial] fn test_exception_when_running_unused_without_lock_file() { - todo!() + let tear_down = init_temp_composer(None, None, None, true); + + let mut app_tester = get_application_tester(); + let err = app_tester + .run( + input(vec![ + ("command", PhpMixed::from("remove")), + ("--unused", PhpMixed::from(true)), + ]), + RunOptions::default(), + ) + .expect_err("expected UnexpectedValueException when --unused without lock file"); + assert!( + err.to_string() + .contains("A valid composer.lock file is required to run this command with --unused"), + "got: {}", + err + ); + + drop(tear_down); } -#[ignore = "missing TestCase::init_temp_composer, create_installed_json, and get_application_tester (ApplicationTester) infrastructure"] +#[ignore = "remove runs post-remove Installer; panics at crates/shirabe/src/composer.rs:500:36 'RefCell already mutably borrowed' (ComposerHandle::borrow during active borrow_mut) in unported install/update path"] #[test] +#[serial] fn test_warning_when_removing_non_existent_package() { - todo!() + let tear_down = init_temp_composer(None, None, None, true); + create_installed_json(&[], &[], true); + + let mut app_tester = get_application_tester(); + let status_code = app_tester + .run( + input(vec![ + ("command", PhpMixed::from("remove")), + ( + "packages", + PhpMixed::List(vec![PhpMixed::from("vendor1/package1")]), + ), + ]), + RunOptions::default(), + ) + .unwrap(); + assert_eq!(SUCCESS, status_code); + assert!( + app_tester.get_display().trim().starts_with( + "<warning>vendor1/package1 is not required in your composer.json and has not been removed</warning>" + ), + "got: {}", + app_tester.get_display() + ); + + drop(tear_down); } -#[ignore = "missing TestCase::init_temp_composer and get_application_tester (ApplicationTester) infrastructure"] #[test] +#[serial] fn test_warning_when_removing_package_from_wrong_type() { - todo!() + let tear_down = init_temp_composer( + Some(&serde_json::json!({ + "require": { "root/req": "1.*" }, + })), + None, + None, + true, + ); + + let mut app_tester = get_application_tester(); + let status_code = app_tester + .run( + input(vec![ + ("command", PhpMixed::from("remove")), + ("packages", PhpMixed::List(vec![PhpMixed::from("root/req")])), + ("--dev", PhpMixed::from(true)), + ("--no-update", PhpMixed::from(true)), + ("--no-interaction", PhpMixed::from(true)), + ]), + RunOptions::default(), + ) + .unwrap(); + assert_eq!(SUCCESS, status_code); + assert_eq!( + "<warning>root/req could not be found in require-dev but it is present in require</warning> +./composer.json has been updated", + app_tester.get_display().trim() + ); + assert_eq!( + serde_json::json!({ "require": { "root/req": "1.*" } }), + read_json_file("./composer.json") + ); + + drop(tear_down); } -#[ignore = "missing TestCase::init_temp_composer and get_application_tester (ApplicationTester) infrastructure"] #[test] +#[serial] fn test_warning_when_removing_package_with_deprecated_dependencies_flag() { - todo!() + let tear_down = init_temp_composer( + Some(&serde_json::json!({ + "require": { "root/req": "1.*" }, + })), + None, + None, + true, + ); + + let mut app_tester = get_application_tester(); + let status_code = app_tester + .run( + input(vec![ + ("command", PhpMixed::from("remove")), + ("packages", PhpMixed::List(vec![PhpMixed::from("root/req")])), + ("--update-with-dependencies", PhpMixed::from(true)), + ("--no-update", PhpMixed::from(true)), + ("--no-interaction", PhpMixed::from(true)), + ]), + RunOptions::default(), + ) + .unwrap(); + assert_eq!(SUCCESS, status_code); + assert_eq!( + "<warning>You are using the deprecated option \"update-with-dependencies\". This is now default behaviour. The --no-update-with-dependencies option can be used to remove a package without its dependencies.</warning> +./composer.json has been updated", + app_tester.get_display().trim() + ); + assert!( + is_empty_json(&read_json_file("./composer.json")), + "expected composer.json to be empty, got: {}", + read_json_file("./composer.json") + ); + + drop(tear_down); } -#[ignore = "missing TestCase::init_temp_composer, create_installed_json, create_composer_lock, and get_application_tester (ApplicationTester) infrastructure"] #[test] +#[serial] fn test_message_output_when_no_unused_packages_to_remove() { - todo!() + let tear_down = init_temp_composer( + Some(&serde_json::json!({ + "repositories": { + "packages": { + "type": "package", + "package": [ + {"name": "root/req", "version": "1.0.0", "require": {"nested/req": "^1"}}, + {"name": "nested/req", "version": "1.1.0"}, + ], + }, + }, + "require": { "root/req": "1.*" }, + })), + None, + None, + true, + ); + + let required_package = get_package("root/req", "1.0.0"); + let mut requires = IndexMap::new(); + requires.insert( + "nested/req".to_string(), + Link::new( + "root/req".to_string(), + "nested/req".to_string(), + AnyConstraint::MatchAll(MatchAllConstraint::new(None)), + Some(Link::TYPE_REQUIRE.to_string()), + "^1".to_string(), + ), + ); + required_package.__set_requires(requires); + let nested_package = get_package("nested/req", "1.1.0"); + + let packages: Vec<PackageInterfaceHandle> = + vec![required_package.clone(), nested_package.clone()]; + create_installed_json(&packages, &[], true); + create_composer_lock(&packages, &[]); + + let mut app_tester = get_application_tester(); + let status_code = app_tester + .run( + input(vec![ + ("command", PhpMixed::from("remove")), + ("--unused", PhpMixed::from(true)), + ("--no-audit", PhpMixed::from(true)), + ("--no-interaction", PhpMixed::from(true)), + ]), + RunOptions::default(), + ) + .unwrap(); + assert_eq!(SUCCESS, status_code); + assert_eq!( + "No unused packages to remove", + app_tester.get_display().trim() + ); + + drop(tear_down); } -#[ignore = "missing TestCase::init_temp_composer, create_installed_json, create_composer_lock, and get_application_tester (ApplicationTester) infrastructure"] +#[ignore = "remove runs post-remove Installer; panics at crates/shirabe/src/composer.rs:500:36 'RefCell already mutably borrowed' (ComposerHandle::borrow during active borrow_mut) in unported install/update path"] #[test] +#[serial] fn test_remove_unused_package() { - todo!() + let tear_down = init_temp_composer( + Some(&serde_json::json!({ + "repositories": { + "packages": { + "type": "package", + "package": [ + {"name": "root/req", "version": "1.0.0"}, + {"name": "not/req", "version": "1.0.0"}, + ], + }, + }, + "require": { "root/req": "1.*" }, + })), + None, + None, + true, + ); + + let required_package = get_package("root/req", "1.0.0"); + let extraneous_package = get_package("not/req", "1.0.0"); + + create_installed_json(&[required_package.clone()], &[], true); + create_composer_lock(&[required_package.clone(), extraneous_package.clone()], &[]); + + let mut app_tester = get_application_tester(); + let status_code = app_tester + .run( + input(vec![ + ("command", PhpMixed::from("remove")), + ("--unused", PhpMixed::from(true)), + ("--no-audit", PhpMixed::from(true)), + ("--no-interaction", PhpMixed::from(true)), + ]), + RunOptions::default(), + ) + .unwrap(); + assert_eq!(SUCCESS, status_code); + assert!( + app_tester.get_display().starts_with( + "<warning>not/req is not required in your composer.json and has not been removed</warning>" + ), + "got: {}", + app_tester.get_display() + ); + assert!( + app_tester + .get_display() + .contains("Running composer update not/req"), + "got: {}", + app_tester.get_display() + ); + assert!( + app_tester + .get_display() + .contains("- Removing not/req (1.0.0)"), + "got: {}", + app_tester.get_display() + ); + + drop(tear_down); } -#[ignore = "missing TestCase::init_temp_composer, create_installed_json, create_composer_lock, and get_application_tester (ApplicationTester) infrastructure"] +#[ignore = "remove runs post-remove Installer; panics at crates/shirabe/src/composer.rs:500:36 'RefCell already mutably borrowed' (ComposerHandle::borrow during active borrow_mut) in unported install/update path"] #[test] +#[serial] fn test_remove_package_by_name() { - todo!() + let tear_down = init_temp_composer( + Some(&serde_json::json!({ + "repositories": { + "packages": { + "type": "package", + "package": [ + {"name": "root/req", "version": "1.0.0", "type": "metapackage"}, + {"name": "root/another", "version": "1.0.0", "type": "metapackage"}, + ], + }, + }, + "require": { "root/req": "1.*", "root/another": "1.*" }, + })), + None, + None, + true, + ); + let root_req_package = get_package("root/req", "1.0.0"); + let root_another_package = get_package("root/another", "1.0.0"); + // Set as a metapackage so that we can do the whole post-remove update & install process without + // Composer trying to download them (DownloadManager::getDownloaderForPackage). + root_req_package.__set_type("metapackage".to_string()); + root_another_package.__set_type("metapackage".to_string()); + + let packages: Vec<PackageInterfaceHandle> = + vec![root_req_package.clone(), root_another_package.clone()]; + create_installed_json(&packages, &[], true); + create_composer_lock(&packages, &[]); + + let mut app_tester = get_application_tester(); + let status_code = app_tester + .run( + input(vec![ + ("command", PhpMixed::from("remove")), + ("packages", PhpMixed::List(vec![PhpMixed::from("root/req")])), + ("--no-audit", PhpMixed::from(true)), + ("--no-interaction", PhpMixed::from(true)), + ]), + RunOptions::default(), + ) + .unwrap(); + assert_eq!(SUCCESS, status_code); + let display = app_tester.get_display(); + let trimmed = display.trim(); + assert!( + trimmed.starts_with("./composer.json has been updated"), + "got: {}", + display + ); + assert!( + trimmed.contains("Running composer update root/req"), + "got: {}", + display + ); + assert!( + trimmed.contains("Lock file operations: 0 installs, 0 updates, 1 removal"), + "got: {}", + display + ); + assert!( + trimmed.contains("- Removing root/req (1.0.0)"), + "got: {}", + display + ); + assert!( + trimmed.contains("Package operations: 0 installs, 0 updates, 1 removal"), + "got: {}", + display + ); + assert_eq!( + serde_json::json!({ "root/another": "1.*" }), + read_json_file("./composer.json")["require"] + ); + assert_eq!( + serde_json::json!([{"name": "root/another", "version": "1.0.0", "type": "metapackage"}]), + read_json_file("./composer.lock")["packages"] + ); + + drop(tear_down); } -#[ignore = "missing TestCase::init_temp_composer, create_installed_json, create_composer_lock, and get_application_tester (ApplicationTester) infrastructure"] +#[ignore = "remove runs post-remove Installer; panics at crates/shirabe/src/composer.rs:500:36 'RefCell already mutably borrowed' (ComposerHandle::borrow during active borrow_mut) in unported install/update path"] #[test] +#[serial] fn test_remove_package_by_name_with_dry_run() { - todo!() + let tear_down = init_temp_composer( + Some(&serde_json::json!({ + "repositories": { + "packages": { + "type": "package", + "package": [ + {"name": "root/req", "version": "1.0.0", "type": "metapackage"}, + {"name": "root/another", "version": "1.0.0", "type": "metapackage"}, + ], + }, + }, + "require": { "root/req": "1.*", "root/another": "1.*" }, + })), + None, + None, + true, + ); + let root_req_package = get_package("root/req", "1.0.0"); + let root_another_package = get_package("root/another", "1.0.0"); + root_req_package.__set_type("metapackage".to_string()); + root_another_package.__set_type("metapackage".to_string()); + + let packages: Vec<PackageInterfaceHandle> = + vec![root_req_package.clone(), root_another_package.clone()]; + create_installed_json(&packages, &[], true); + create_composer_lock(&packages, &[]); + + let mut app_tester = get_application_tester(); + let status_code = app_tester + .run( + input(vec![ + ("command", PhpMixed::from("remove")), + ("packages", PhpMixed::List(vec![PhpMixed::from("root/req")])), + ("--dry-run", PhpMixed::from(true)), + ("--no-audit", PhpMixed::from(true)), + ("--no-interaction", PhpMixed::from(true)), + ]), + RunOptions::default(), + ) + .unwrap(); + assert_eq!(SUCCESS, status_code); + let display = app_tester.get_display(); + let trimmed = display.trim(); + assert!( + trimmed.contains("./composer.json has been updated"), + "got: {}", + display + ); + assert!( + trimmed.contains("Running composer update root/req"), + "got: {}", + display + ); + assert!( + trimmed.contains("Lock file operations: 0 installs, 0 updates, 1 removal"), + "got: {}", + display + ); + assert!( + trimmed.contains("- Removing root/req (1.0.0)"), + "got: {}", + display + ); + assert!( + trimmed.contains("Package operations: 0 installs, 0 updates, 1 removal"), + "got: {}", + display + ); + assert_eq!( + serde_json::json!({ "root/req": "1.*", "root/another": "1.*" }), + read_json_file("./composer.json")["require"] + ); + assert_eq!( + serde_json::json!([ + {"name": "root/another", "version": "1.0.0", "type": "metapackage"}, + {"name": "root/req", "version": "1.0.0", "type": "metapackage"}, + ]), + read_json_file("./composer.lock")["packages"] + ); + + drop(tear_down); } -#[ignore = "missing TestCase::init_temp_composer, create_installed_json, create_composer_lock, and get_application_tester (ApplicationTester) infrastructure"] +#[ignore = "remove runs post-remove Installer; panics at crates/shirabe/src/composer.rs:500:36 'RefCell already mutably borrowed' (ComposerHandle::borrow during active borrow_mut) in unported install/update path"] #[test] +#[serial] fn test_remove_allowed_plugin_package_with_no_other_allowed_plugins() { - todo!() + let tear_down = init_temp_composer( + Some(&serde_json::json!({ + "repositories": { + "packages": { + "type": "package", + "package": [ + {"name": "root/req", "version": "1.0.0", "type": "metapackage"}, + {"name": "root/another", "version": "1.0.0", "type": "metapackage"}, + ], + }, + }, + "require": { "root/req": "1.*", "root/another": "1.*" }, + "config": { "allow-plugins": { "root/req": true } }, + })), + None, + None, + true, + ); + let root_req_package = get_package("root/req", "1.0.0"); + let root_another_package = get_package("root/another", "1.0.0"); + root_req_package.__set_type("metapackage".to_string()); + root_another_package.__set_type("metapackage".to_string()); + + let packages: Vec<PackageInterfaceHandle> = + vec![root_req_package.clone(), root_another_package.clone()]; + create_installed_json(&packages, &[], true); + create_composer_lock(&packages, &[]); + + let mut app_tester = get_application_tester(); + let status_code = app_tester + .run( + input(vec![ + ("command", PhpMixed::from("remove")), + ("packages", PhpMixed::List(vec![PhpMixed::from("root/req")])), + ("--no-audit", PhpMixed::from(true)), + ("--no-interaction", PhpMixed::from(true)), + ]), + RunOptions::default(), + ) + .unwrap(); + assert_eq!(SUCCESS, status_code); + assert_eq!( + serde_json::json!({ "root/another": "1.*" }), + read_json_file("./composer.json")["require"] + ); + assert!( + is_empty_json(&read_json_file("./composer.json")["config"]), + "expected config to be empty, got: {}", + read_json_file("./composer.json")["config"] + ); + + drop(tear_down); } -#[ignore = "missing TestCase::init_temp_composer and get_application_tester (ApplicationTester) infrastructure"] +#[ignore = "remove runs post-remove Installer; panics at crates/shirabe/src/composer.rs:500:36 'RefCell already mutably borrowed' (ComposerHandle::borrow during active borrow_mut) in unported install/update path"] #[test] +#[serial] fn test_remove_allowed_plugin_package_with_other_allowed_plugins() { - todo!() + let tear_down = init_temp_composer( + Some(&serde_json::json!({ + "repositories": { + "packages": { + "type": "package", + "package": [ + {"name": "root/req", "version": "1.0.0", "type": "metapackage"}, + {"name": "root/another", "version": "1.0.0", "type": "metapackage"}, + ], + }, + }, + "require": { "root/req": "1.*", "root/another": "1.*" }, + "config": { "allow-plugins": { "root/another": true, "root/req": true } }, + })), + None, + None, + true, + ); + let root_req_package = get_package("root/req", "1.0.0"); + let root_another_package = get_package("root/another", "1.0.0"); + root_req_package.__set_type("metapackage".to_string()); + root_another_package.__set_type("metapackage".to_string()); + + let mut app_tester = get_application_tester(); + let status_code = app_tester + .run( + input(vec![ + ("command", PhpMixed::from("remove")), + ("packages", PhpMixed::List(vec![PhpMixed::from("root/req")])), + ("--no-audit", PhpMixed::from(true)), + ("--no-interaction", PhpMixed::from(true)), + ]), + RunOptions::default(), + ) + .unwrap(); + assert_eq!(SUCCESS, status_code); + assert_eq!( + serde_json::json!({ "root/another": "1.*" }), + read_json_file("./composer.json")["require"] + ); + assert_eq!( + serde_json::json!({ "allow-plugins": { "root/another": true } }), + read_json_file("./composer.json")["config"] + ); + + drop(tear_down); } -#[ignore = "missing TestCase::init_temp_composer, create_installed_json, create_composer_lock, and get_application_tester (ApplicationTester) infrastructure"] +#[ignore = "remove runs post-remove Installer; panics at crates/shirabe/src/composer.rs:500:36 'RefCell already mutably borrowed' (ComposerHandle::borrow during active borrow_mut) in unported install/update path"] #[test] +#[serial] fn test_remove_packages_by_vendor() { - todo!() + let tear_down = init_temp_composer( + Some(&serde_json::json!({ + "repositories": { + "packages": { + "type": "package", + "package": [ + {"name": "root/req", "version": "1.0.0"}, + {"name": "root/another", "version": "1.0.0"}, + {"name": "another/req", "version": "1.0.0"}, + ], + }, + }, + "require": { "root/req": "1.*", "root/another": "1.*", "another/req": "1.*" }, + })), + None, + None, + true, + ); + let root_req_package = get_package("root/req", "1.0.0"); + let root_another_package = get_package("root/another", "1.0.0"); + let another_req_package = get_package("another/req", "1.0.0"); + + let packages: Vec<PackageInterfaceHandle> = vec![ + root_req_package.clone(), + root_another_package.clone(), + another_req_package.clone(), + ]; + create_installed_json(&packages, &[], true); + create_composer_lock(&packages, &[]); + + let mut app_tester = get_application_tester(); + let status_code = app_tester + .run( + input(vec![ + ("command", PhpMixed::from("remove")), + ("packages", PhpMixed::List(vec![PhpMixed::from("root/*")])), + ("--no-install", PhpMixed::from(true)), + ("--no-audit", PhpMixed::from(true)), + ("--no-interaction", PhpMixed::from(true)), + ]), + RunOptions::default(), + ) + .unwrap(); + assert_eq!(SUCCESS, status_code); + let display = app_tester.get_display(); + assert!( + display + .trim() + .starts_with("./composer.json has been updated"), + "got: {}", + display + ); + assert!( + display.contains("Running composer update root/*"), + "got: {}", + display + ); + assert!( + display.contains("- Removing root/another (1.0.0)"), + "got: {}", + display + ); + assert!( + display.contains("- Removing root/req (1.0.0)"), + "got: {}", + display + ); + assert!(display.contains("Writing lock file"), "got: {}", display); + assert_eq!( + serde_json::json!({ "another/req": "1.*" }), + read_json_file("./composer.json")["require"] + ); + assert_eq!( + serde_json::json!([{"name": "another/req", "version": "1.0.0", "type": "library"}]), + read_json_file("./composer.lock")["packages"] + ); + + drop(tear_down); } -#[ignore = "missing TestCase::init_temp_composer, create_installed_json, create_composer_lock, and get_application_tester (ApplicationTester) infrastructure"] +#[ignore = "remove runs post-remove Installer; panics at crates/shirabe/src/composer.rs:500:36 'RefCell already mutably borrowed' (ComposerHandle::borrow during active borrow_mut) in unported install/update path"] #[test] +#[serial] fn test_remove_packages_by_vendor_with_dry_run() { - todo!() + let tear_down = init_temp_composer( + Some(&serde_json::json!({ + "repositories": { + "packages": { + "type": "package", + "package": [ + {"name": "root/req", "version": "1.0.0"}, + {"name": "root/another", "version": "1.0.0"}, + {"name": "another/req", "version": "1.0.0"}, + ], + }, + }, + "require": { "root/req": "1.*", "root/another": "1.*", "another/req": "1.*" }, + })), + None, + None, + true, + ); + let root_req_package = get_package("root/req", "1.0.0"); + let root_another_package = get_package("root/another", "1.0.0"); + let another_req_package = get_package("another/req", "1.0.0"); + + let packages: Vec<PackageInterfaceHandle> = vec![ + root_req_package.clone(), + root_another_package.clone(), + another_req_package.clone(), + ]; + create_installed_json(&packages, &[], true); + create_composer_lock(&packages, &[]); + + let mut app_tester = get_application_tester(); + app_tester + .run( + input(vec![ + ("command", PhpMixed::from("remove")), + ("packages", PhpMixed::List(vec![PhpMixed::from("root/*")])), + ("--dry-run", PhpMixed::from(true)), + ("--no-install", PhpMixed::from(true)), + ("--no-audit", PhpMixed::from(true)), + ("--no-interaction", PhpMixed::from(true)), + ]), + RunOptions::default(), + ) + .unwrap(); + assert_eq!(SUCCESS, app_tester.get_status_code()); + assert_eq!( + "./composer.json has been updated +Running composer update root/* +Loading composer repositories with package information +Updating dependencies +Lock file operations: 0 installs, 0 updates, 2 removals + - Removing root/another (1.0.0) + - Removing root/req (1.0.0)", + app_tester.get_display().trim() + ); + assert!( + !app_tester.get_display().contains("Writing lock file"), + "got: {}", + app_tester.get_display() + ); + assert_eq!( + serde_json::json!({ "root/req": "1.*", "root/another": "1.*", "another/req": "1.*" }), + read_json_file("./composer.json")["require"] + ); + assert_eq!( + serde_json::json!([ + {"name": "another/req", "version": "1.0.0", "type": "library"}, + {"name": "root/another", "version": "1.0.0", "type": "library"}, + {"name": "root/req", "version": "1.0.0", "type": "library"}, + ]), + read_json_file("./composer.lock")["packages"] + ); + + drop(tear_down); } -#[ignore = "missing TestCase::init_temp_composer and get_application_tester (ApplicationTester) infrastructure"] #[test] +#[serial] fn test_warning_when_removing_packages_by_vendor_from_wrong_type() { - todo!() + let tear_down = init_temp_composer( + Some(&serde_json::json!({ + "require": { "root/req": "1.*", "root/another": "1.*", "another/req": "1.*" }, + })), + None, + None, + true, + ); + + let mut app_tester = get_application_tester(); + let status_code = app_tester + .run( + input(vec![ + ("command", PhpMixed::from("remove")), + ("packages", PhpMixed::List(vec![PhpMixed::from("root/*")])), + ("--dev", PhpMixed::from(true)), + ("--no-interaction", PhpMixed::from(true)), + ("--no-update", PhpMixed::from(true)), + ]), + RunOptions::default(), + ) + .unwrap(); + assert_eq!(SUCCESS, status_code); + assert_eq!( + "<warning>root/req could not be found in require-dev but it is present in require</warning> +<warning>root/another could not be found in require-dev but it is present in require</warning> +./composer.json has been updated", + app_tester.get_display().trim() + ); + assert_eq!( + serde_json::json!({ + "require": { "root/req": "1.*", "root/another": "1.*", "another/req": "1.*" } + }), + read_json_file("./composer.json") + ); + + drop(tear_down); } -#[ignore = "missing TestCase::init_temp_composer, create_installed_json, create_composer_lock, and get_application_tester (ApplicationTester) infrastructure"] +#[ignore = "remove runs post-remove Installer; panics at crates/shirabe/src/composer.rs:500:36 'RefCell already mutably borrowed' (ComposerHandle::borrow during active borrow_mut) in unported install/update path"] #[test] +#[serial] fn test_package_still_present_error_when_no_install_flag_used() { - todo!() + let tear_down = init_temp_composer( + Some(&serde_json::json!({ + "require": { "root/req": "1.*" }, + })), + None, + None, + true, + ); + let root_req_package = get_package("root/req", "1.0.0"); + + create_installed_json(&[root_req_package.clone()], &[], true); + create_composer_lock(&[root_req_package.clone()], &[]); + + let mut app_tester = get_application_tester(); + let status_code = app_tester + .run( + input(vec![ + ("command", PhpMixed::from("remove")), + ("packages", PhpMixed::List(vec![PhpMixed::from("root/req")])), + ("--no-install", PhpMixed::from(true)), + ("--no-audit", PhpMixed::from(true)), + ("--no-interaction", PhpMixed::from(true)), + ]), + RunOptions::default(), + ) + .unwrap(); + assert_eq!(INVALID, status_code); + let display = app_tester.get_display(); + assert!( + display.contains("./composer.json has been updated"), + "got: {}", + display + ); + assert!( + display.contains("Lock file operations: 0 installs, 0 updates, 1 removal"), + "got: {}", + display + ); + assert!( + display.contains("- Removing root/req (1.0.0)"), + "got: {}", + display + ); + assert!(display.contains("Writing lock file"), "got: {}", display); + assert!( + display.contains( + "Removal failed, root/req is still present, it may be required by another package. See `composer why root/req`" + ), + "got: {}", + display + ); + assert!( + is_empty_json(&read_json_file("./composer.json")), + "expected composer.json to be empty, got: {}", + read_json_file("./composer.json") + ); + assert!( + is_empty_json(&read_json_file("./composer.lock")["packages"]), + "expected composer.lock packages to be empty, got: {}", + read_json_file("./composer.lock")["packages"] + ); + assert_eq!( + serde_json::json!([{ + "name": "root/req", + "version": "1.0.0", + "version_normalized": "1.0.0.0", + "type": "library", + "install-path": "../root/req" + }]), + read_json_file("./vendor/composer/installed.json")["packages"] + ); + + drop(tear_down); +} + +/// ref: RemoveCommandTest::testUpdateInheritedDependenciesFlagIsPassedToPostRemoveInstaller +/// (data provider provideInheritedDependenciesUpdateFlag). +fn run_update_inherited_dependencies_flag_case( + install_flag_name: &str, + expected_composer_update_command: &str, +) { + let tear_down = init_temp_composer( + Some(&serde_json::json!({ + "repositories": { + "packages": { + "type": "package", + "package": [ + {"name": "root/req", "version": "1.0.0", "type": "metapackage"}, + ], + }, + }, + "require": { "root/req": "1.*" }, + })), + None, + None, + true, + ); + let root_req_package = get_package("root/req", "1.0.0"); + root_req_package.__set_type("metapackage".to_string()); + + create_installed_json(&[root_req_package.clone()], &[], true); + create_composer_lock(&[root_req_package.clone()], &[]); + + let mut app_tester = get_application_tester(); + let status_code = app_tester + .run( + input(vec![ + ("command", PhpMixed::from("remove")), + ("packages", PhpMixed::List(vec![PhpMixed::from("root/req")])), + (install_flag_name, PhpMixed::from(true)), + ("--no-audit", PhpMixed::from(true)), + ("--no-interaction", PhpMixed::from(true)), + ]), + RunOptions::default(), + ) + .unwrap(); + assert_eq!(SUCCESS, status_code); + let display = app_tester.get_display(); + assert!( + display.contains("./composer.json has been updated"), + "got: {}", + display + ); + assert!( + display.contains(expected_composer_update_command), + "got: {}", + display + ); + assert!( + display.contains("Package operations: 0 installs, 0 updates, 1 removal"), + "got: {}", + display + ); + assert!( + display.contains("- Removing root/req (1.0.0)"), + "got: {}", + display + ); + assert!(display.contains("Writing lock file"), "got: {}", display); + assert!( + display.contains("Lock file operations: 0 installs, 0 updates, 1 removal"), + "got: {}", + display + ); + assert!( + is_empty_json(&read_json_file("./composer.lock")["packages"]), + "expected composer.lock packages to be empty, got: {}", + read_json_file("./composer.lock")["packages"] + ); + + drop(tear_down); } -#[ignore = "missing TestCase::init_temp_composer, create_installed_json, create_composer_lock, and get_application_tester (ApplicationTester) infrastructure"] +#[ignore = "remove runs post-remove Installer; panics at crates/shirabe/src/composer.rs:500:36 'RefCell already mutably borrowed' (ComposerHandle::borrow during active borrow_mut) in unported install/update path"] #[test] +#[serial] fn test_update_inherited_dependencies_flag_is_passed_to_post_remove_installer() { - todo!() + // 'update with all dependencies' + run_update_inherited_dependencies_flag_case( + "--update-with-all-dependencies", + "Running composer update root/req --with-all-dependencies", + ); + // 'with all dependencies' + run_update_inherited_dependencies_flag_case( + "--with-all-dependencies", + "Running composer update root/req --with-all-dependencies", + ); + // 'no update with dependencies' + run_update_inherited_dependencies_flag_case( + "--no-update-with-dependencies", + "Running composer update root/req --with-dependencies", + ); } |
