diff options
| author | nsfisis <nsfisis@gmail.com> | 2026-06-22 02:09:59 +0900 |
|---|---|---|
| committer | nsfisis <nsfisis@gmail.com> | 2026-06-22 02:09:59 +0900 |
| commit | 822d9a872807a92a5337ee8b7bab96dc9845cdbb (patch) | |
| tree | 4931365df5978caffade69cad2154718a9076f66 /crates/shirabe | |
| parent | f691b864b687f251c3b266e8cff2774d730567ba (diff) | |
| download | php-shirabe-822d9a872807a92a5337ee8b7bab96dc9845cdbb.tar.gz php-shirabe-822d9a872807a92a5337ee8b7bab96dc9845cdbb.tar.zst php-shirabe-822d9a872807a92a5337ee8b7bab96dc9845cdbb.zip | |
test: port more test cases
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Diffstat (limited to 'crates/shirabe')
118 files changed, 13263 insertions, 1324 deletions
diff --git a/crates/shirabe/tests/advisory/auditor_test.rs b/crates/shirabe/tests/advisory/auditor_test.rs index ea0b3c8..038c675 100644 --- a/crates/shirabe/tests/advisory/auditor_test.rs +++ b/crates/shirabe/tests/advisory/auditor_test.rs @@ -1,33 +1,171 @@ //! ref: composer/tests/Composer/Test/Advisory/AuditorTest.php +use chrono::Utc; +use indexmap::IndexMap; +use shirabe::advisory::AnySecurityAdvisory; +use shirabe::advisory::Auditor; +use shirabe::advisory::PartialSecurityAdvisory; +use shirabe::advisory::SecurityAdvisory; +use shirabe_semver::constraint::SimpleConstraint; + +fn constraint(operator: &str, version: &str) -> shirabe_semver::constraint::AnyConstraint { + SimpleConstraint::new(operator.to_string(), version.to_string(), None).into() +} + +fn full_advisory() -> AnySecurityAdvisory { + let mut source: IndexMap<String, String> = IndexMap::new(); + source.insert("name".to_string(), "foo".to_string()); + source.insert("remoteId".to_string(), "remoteID".to_string()); + AnySecurityAdvisory::Full(SecurityAdvisory::new( + "foo/bar".to_string(), + "123".to_string(), + constraint("=", "1.0.0.0"), + "test".to_string(), + vec![source], + Utc::now(), + None, + None, + None, + )) +} + +fn full_advisory_with_id(advisory_id: &str) -> AnySecurityAdvisory { + let mut source: IndexMap<String, String> = IndexMap::new(); + source.insert("name".to_string(), "foo".to_string()); + source.insert("remoteId".to_string(), "remoteID".to_string()); + AnySecurityAdvisory::Full(SecurityAdvisory::new( + "foo/bar".to_string(), + advisory_id.to_string(), + constraint("=", "1.0.0.0"), + "test".to_string(), + vec![source], + Utc::now(), + None, + None, + None, + )) +} + +fn partial_advisory(advisory_id: &str) -> AnySecurityAdvisory { + AnySecurityAdvisory::Partial(PartialSecurityAdvisory::new( + "foo/bar".to_string(), + advisory_id.to_string(), + constraint("=", "1.0.0.0"), + )) +} + +fn ignore_list(pairs: Vec<(&str, Option<&str>)>) -> IndexMap<String, Option<String>> { + pairs + .into_iter() + .map(|(k, v)| (k.to_string(), v.map(String::from))) + .collect() +} + // These run the Auditor against a mocked HttpDownloader/IO and packages built from version // constraints (parsed via a look-around regex the regex crate cannot compile). #[test] -#[ignore = "not yet ported (Auditor with mocked HttpDownloader/IO; constraint parsing uses a look-around regex)"] +#[ignore = "requires PHPUnit getMockBuilder partial mock of ComposerRepository (hasSecurityAdvisories/getSecurityAdvisories) and BufferIO; no mocking infrastructure exists"] fn test_audit() { todo!() } #[test] -#[ignore = "not yet ported (Auditor with mocked HttpDownloader/IO; constraint parsing uses a look-around regex)"] +#[ignore = "requires getIOMock with expects() output expectations and getMockBuilder mock of ComposerRepository; no mocking infrastructure exists"] fn test_audit_with_ignore() { todo!() } #[test] -#[ignore = "not yet ported (Auditor with mocked HttpDownloader/IO; constraint parsing uses a look-around regex)"] +#[ignore = "requires getMockBuilder partial mock of RepositorySet (getMatchingSecurityAdvisories willReturnCallback); no mocking infrastructure exists"] fn test_audit_with_ignore_unreachable() { todo!() } #[test] -#[ignore = "not yet ported (Auditor with mocked HttpDownloader/IO; constraint parsing uses a look-around regex)"] +#[ignore = "requires getIOMock with expects() output expectations and getMockBuilder mock of ComposerRepository; no mocking infrastructure exists"] fn test_audit_with_ignore_severity() { todo!() } #[test] -#[ignore = "not yet ported (Auditor with mocked HttpDownloader/IO; constraint parsing uses a look-around regex)"] +#[ignore] fn test_needs_complete_advisory_load() { - todo!() + let cases: Vec<( + IndexMap<String, Vec<AnySecurityAdvisory>>, + IndexMap<String, Option<String>>, + bool, + )> = vec![ + // no filter or advisories + (IndexMap::new(), ignore_list(vec![]), false), + // packagist filters are IDs so work fine with partial advisories + ( + IndexMap::new(), + ignore_list(vec![("PKSA-foo-bar", None)]), + false, + ), + // packagist filters are IDs so work fine with partial advisories/2 + ( + { + let mut m: IndexMap<String, Vec<AnySecurityAdvisory>> = IndexMap::new(); + m.insert( + "vendor1/package1".to_string(), + vec![full_advisory(), partial_advisory("1234")], + ); + m + }, + ignore_list(vec![("PKSA-foo-bar", Some("this is fine 🔥"))]), + false, + ), + // no advisories no need to load any further + ( + IndexMap::new(), + ignore_list(vec![("CVE-2025-1234", None)]), + false, + ), + // no advisories no need to load any further/2 + ( + { + let mut m: IndexMap<String, Vec<AnySecurityAdvisory>> = IndexMap::new(); + m.insert("vendor1/package1".to_string(), vec![]); + m + }, + ignore_list(vec![("CVE-2025-1234", None)]), + false, + ), + // CVE filter or other non-packagist ones might need to fully load for safety if partial advisories are present + ( + { + let mut m: IndexMap<String, Vec<AnySecurityAdvisory>> = IndexMap::new(); + m.insert( + "vendor1/package1".to_string(), + vec![full_advisory(), partial_advisory("1234")], + ); + m + }, + ignore_list(vec![("CVE-2025-1234", None)]), + true, + ), + // filter does not trigger load if all advisories are fully loaded + ( + { + let mut m: IndexMap<String, Vec<AnySecurityAdvisory>> = IndexMap::new(); + m.insert("vendor1/package1".to_string(), vec![full_advisory()]); + m.insert( + "vendor1/package2".to_string(), + vec![full_advisory_with_id("1234")], + ); + m + }, + ignore_list(vec![("CVE-2025-1234", None)]), + false, + ), + ]; + + let auditor = Auditor; + for (advisories, ignore_list, expected) in cases { + assert_eq!( + expected, + auditor.needs_complete_advisory_load(&advisories, &ignore_list) + ); + } } diff --git a/crates/shirabe/tests/all_functional_test.rs b/crates/shirabe/tests/all_functional_test.rs index 22e495a..98b800b 100644 --- a/crates/shirabe/tests/all_functional_test.rs +++ b/crates/shirabe/tests/all_functional_test.rs @@ -22,14 +22,14 @@ impl Drop for TearDown { } #[test] -#[ignore = "not yet ported (builds composer.phar and runs the functional .test fixtures via the binary)"] +#[ignore = "depends on unported bin/compile phar build (./bin/compile) and running composer.phar as a subprocess"] fn test_build_phar() { let _tear_down = TearDown; todo!() } #[test] -#[ignore = "not yet ported (builds composer.phar and runs the functional .test fixtures via the binary)"] +#[ignore = "depends on unported functional-test harness (parseTestFile/cleanOutput) running the built composer.phar as a subprocess"] fn test_integration() { let _tear_down = TearDown; todo!() diff --git a/crates/shirabe/tests/application_test.rs b/crates/shirabe/tests/application_test.rs index 856c1b7..dddf8de 100644 --- a/crates/shirabe/tests/application_test.rs +++ b/crates/shirabe/tests/application_test.rs @@ -1,9 +1,22 @@ //! ref: composer/tests/Composer/Test/ApplicationTest.php -// These drive the console Application (doRun, getDisplay, plugin disabling, command -// resolution) via ApplicationTester, none of which are ported. +// These drive the console Application (doRun, command resolution, plugin disabling). +// The tests needing Application::setAutoExit/setCatchErrors or the initTempComposer +// helper, or a runtime define() of COMPOSER_DEV_WARNING_TIME, remain unportable. +use std::cell::RefCell; +use std::rc::Rc; + +use shirabe::command::about_command::AboutCommand; +use shirabe::command::self_update_command::SelfUpdateCommand; +use shirabe::console::application::Application; use shirabe::util::platform::Platform; +use shirabe_external_packages::symfony::console::command::Command; +use shirabe_external_packages::symfony::console::input::array_input::ArrayInput; +use shirabe_external_packages::symfony::console::input::input_interface::InputInterface; +use shirabe_external_packages::symfony::console::output::buffered_output::BufferedOutput; +use shirabe_external_packages::symfony::console::output::output_interface::OutputInterface; +use shirabe_php_shim::PhpMixed; fn set_up() { Platform::put_env("COMPOSER_DISABLE_XDEBUG_WARN", "1"); @@ -21,8 +34,8 @@ impl Drop for TearDown { } } +#[ignore = "no define() setter exists for the COMPOSER_DEV_WARNING_TIME constant (shim defined() is a fixed matches!)"] #[test] -#[ignore = "requires the console Application/ApplicationTester harness, which is not yet ported"] fn test_dev_warning() { let _tear_down = TearDown; set_up(); @@ -30,26 +43,86 @@ fn test_dev_warning() { todo!() } +#[ignore] #[test] -#[ignore = "requires the console Application/ApplicationTester harness, which is not yet ported"] fn test_dev_warning_suppressed_for_self_update() { let _tear_down = TearDown; set_up(); - todo!() + if Platform::is_windows() { + // markTestSkipped('Does not run on windows') + return; + } + + let application = Rc::new(RefCell::new(Application::new( + "Composer".to_string(), + "".to_string(), + ))); + let command: Rc<RefCell<dyn Command>> = Rc::new(RefCell::new(SelfUpdateCommand::new())); + application.borrow_mut().add(command).unwrap(); + + let output = Rc::new(RefCell::new(BufferedOutput::new(None, false, None))); + let input: Rc<RefCell<dyn InputInterface>> = Rc::new(RefCell::new( + ArrayInput::new( + vec![(PhpMixed::from("command"), PhpMixed::from("self-update"))], + None, + ) + .unwrap(), + )); + let output_trait: Rc<RefCell<dyn OutputInterface>> = output.clone(); + Application::do_run(&application, input, output_trait).unwrap(); + + assert_eq!( + "This instance of Composer does not have the self-update command.\n\ + This could be due to a number of reasons, such as Composer being installed as a system package on your OS, or Composer being installed as a package in the current project.\n", + output.borrow().fetch().as_str() + ); } +#[ignore] #[test] -#[ignore = "requires the console Application/ApplicationTester harness, which is not yet ported"] fn test_process_isolation_works_multiple_times() { let _tear_down = TearDown; set_up(); - todo!() + let application = Rc::new(RefCell::new(Application::new( + "Composer".to_string(), + "".to_string(), + ))); + let command: Rc<RefCell<dyn Command>> = Rc::new(RefCell::new(AboutCommand::new())); + application.borrow_mut().add(command).unwrap(); + + let input1: Rc<RefCell<dyn InputInterface>> = Rc::new(RefCell::new( + ArrayInput::new( + vec![(PhpMixed::from("command"), PhpMixed::from("about"))], + None, + ) + .unwrap(), + )); + let output1: Rc<RefCell<dyn OutputInterface>> = + Rc::new(RefCell::new(BufferedOutput::new(None, false, None))); + assert_eq!( + 0, + Application::do_run(&application, input1, output1).unwrap() + ); + + let input2: Rc<RefCell<dyn InputInterface>> = Rc::new(RefCell::new( + ArrayInput::new( + vec![(PhpMixed::from("command"), PhpMixed::from("about"))], + None, + ) + .unwrap(), + )); + let output2: Rc<RefCell<dyn OutputInterface>> = + Rc::new(RefCell::new(BufferedOutput::new(None, false, None))); + assert_eq!( + 0, + Application::do_run(&application, input2, output2).unwrap() + ); } +#[ignore = "Application::set_auto_exit / set_catch_errors and the initTempComposer test helper do not exist"] #[test] -#[ignore = "requires the console Application/ApplicationTester harness, which is not yet ported"] fn test_no_plugins_disables_plugins_when_script_commands_exist() { let _tear_down = TearDown; set_up(); @@ -57,8 +130,8 @@ fn test_no_plugins_disables_plugins_when_script_commands_exist() { todo!() } +#[ignore = "Application::set_auto_exit / set_catch_errors and the initTempComposer test helper do not exist"] #[test] -#[ignore = "requires the console Application/ApplicationTester harness, which is not yet ported"] fn test_script_command_takes_priority_over_abbreviated_builtin_command() { let _tear_down = TearDown; set_up(); diff --git a/crates/shirabe/tests/autoload/autoload_generator_test.rs b/crates/shirabe/tests/autoload/autoload_generator_test.rs index 0729497..72ac999 100644 --- a/crates/shirabe/tests/autoload/autoload_generator_test.rs +++ b/crates/shirabe/tests/autoload/autoload_generator_test.rs @@ -27,241 +27,241 @@ impl Drop for TearDown { // the generated autoload files. The integration setup (fixtures, mocks, filesystem) is not // ported yet. #[test] -#[ignore = "not yet ported (AutoloadGenerator integration: fixtures, mocked installers and generated-file comparison)"] +#[ignore = "setUp needs mocked Config::get, InstallationManager::getInstallPath, InstalledRepositoryInterface::getCanonicalPackages and TestCase::getUniqueTmpDirectory/ensureDirectoryExistsAndClear; no mock infra exists"] fn test_root_package_autoloading() { todo!() } #[test] -#[ignore = "not yet ported (AutoloadGenerator integration: fixtures, mocked installers and generated-file comparison)"] +#[ignore = "setUp needs mocked Config/InstallationManager/InstalledRepositoryInterface and TestCase tmp-dir helpers; no mock infra exists"] fn test_root_package_dev_autoloading() { todo!() } #[test] -#[ignore = "not yet ported (AutoloadGenerator integration: fixtures, mocked installers and generated-file comparison)"] +#[ignore = "setUp needs mocked Config/InstallationManager/InstalledRepositoryInterface and TestCase tmp-dir helpers; no mock infra exists"] fn test_root_package_dev_autoloading_disabled_by_default() { todo!() } #[test] -#[ignore = "not yet ported (AutoloadGenerator integration: fixtures, mocked installers and generated-file comparison)"] +#[ignore = "setUp needs mocked Config/InstallationManager/InstalledRepositoryInterface and TestCase tmp-dir helpers; no mock infra exists"] fn test_vendor_dir_same_as_working_dir() { todo!() } #[test] -#[ignore = "not yet ported (AutoloadGenerator integration: fixtures, mocked installers and generated-file comparison)"] +#[ignore = "setUp needs mocked Config/InstallationManager/InstalledRepositoryInterface and TestCase tmp-dir helpers; no mock infra exists"] fn test_root_package_autoloading_alternative_vendor_dir() { todo!() } #[test] -#[ignore = "not yet ported (AutoloadGenerator integration: fixtures, mocked installers and generated-file comparison)"] +#[ignore = "setUp needs mocked Config/InstallationManager/InstalledRepositoryInterface and TestCase tmp-dir helpers; no mock infra exists"] fn test_root_package_autoloading_with_target_dir() { todo!() } #[test] -#[ignore = "not yet ported (AutoloadGenerator integration: fixtures, mocked installers and generated-file comparison)"] +#[ignore = "setUp needs mocked Config/InstallationManager/InstalledRepositoryInterface and TestCase tmp-dir helpers; no mock infra exists"] fn test_duplicate_files_warning() { todo!() } #[test] -#[ignore = "not yet ported (AutoloadGenerator integration: fixtures, mocked installers and generated-file comparison)"] +#[ignore = "setUp needs mocked Config/InstallationManager/InstalledRepositoryInterface and TestCase tmp-dir helpers; no mock infra exists"] fn test_vendors_autoloading() { todo!() } #[test] -#[ignore = "not yet ported (AutoloadGenerator integration: fixtures, mocked installers and generated-file comparison)"] +#[ignore = "setUp needs mocked Config/InstallationManager/InstalledRepositoryInterface and TestCase tmp-dir helpers; no mock infra exists"] fn test_vendors_autoloading_with_metapackages() { todo!() } #[test] -#[ignore = "not yet ported (AutoloadGenerator integration: fixtures, mocked installers and generated-file comparison)"] +#[ignore = "setUp needs mocked Config/InstallationManager/InstalledRepositoryInterface and TestCase tmp-dir helpers; no mock infra exists"] fn test_non_dev_autoload_exclusion_with_recursion() { todo!() } #[test] -#[ignore = "not yet ported (AutoloadGenerator integration: fixtures, mocked installers and generated-file comparison)"] +#[ignore = "setUp needs mocked Config/InstallationManager/InstalledRepositoryInterface and TestCase tmp-dir helpers; no mock infra exists"] fn test_non_dev_autoload_should_include_replaced_packages() { todo!() } #[test] -#[ignore = "not yet ported (AutoloadGenerator integration: fixtures, mocked installers and generated-file comparison)"] +#[ignore = "setUp needs mocked Config/InstallationManager/InstalledRepositoryInterface and TestCase tmp-dir helpers; no mock infra exists"] fn test_non_dev_autoload_exclusion_with_recursion_replace() { todo!() } #[test] -#[ignore = "not yet ported (AutoloadGenerator integration: fixtures, mocked installers and generated-file comparison)"] +#[ignore = "setUp needs mocked Config/InstallationManager/InstalledRepositoryInterface and TestCase tmp-dir helpers; no mock infra exists"] fn test_non_dev_autoload_replaces_nested_requirements() { todo!() } #[test] -#[ignore = "not yet ported (AutoloadGenerator integration: fixtures, mocked installers and generated-file comparison)"] +#[ignore = "setUp needs mocked Config/InstallationManager/InstalledRepositoryInterface and TestCase tmp-dir helpers; no mock infra exists"] fn test_phar_autoload() { todo!() } #[test] -#[ignore = "not yet ported (AutoloadGenerator integration: fixtures, mocked installers and generated-file comparison)"] +#[ignore = "setUp needs mocked Config/InstallationManager/InstalledRepositoryInterface and TestCase tmp-dir helpers; no mock infra exists"] fn test_psr_to_class_map_ignores_non_existing_dir() { todo!() } #[test] -#[ignore = "not yet ported (AutoloadGenerator integration: fixtures, mocked installers and generated-file comparison)"] +#[ignore = "setUp needs mocked Config/InstallationManager/InstalledRepositoryInterface and TestCase tmp-dir helpers; no mock infra exists"] fn test_psr_to_class_map_ignores_non_psr_classes() { todo!() } #[test] -#[ignore = "not yet ported (AutoloadGenerator integration: fixtures, mocked installers and generated-file comparison)"] +#[ignore = "setUp needs mocked Config/InstallationManager/InstalledRepositoryInterface and TestCase tmp-dir helpers; no mock infra exists"] fn test_vendors_class_map_autoloading() { todo!() } #[test] -#[ignore = "not yet ported (AutoloadGenerator integration: fixtures, mocked installers and generated-file comparison)"] +#[ignore = "setUp needs mocked Config/InstallationManager/InstalledRepositoryInterface and TestCase tmp-dir helpers; no mock infra exists"] fn test_vendors_class_map_autoloading_with_target_dir() { todo!() } #[test] -#[ignore = "not yet ported (AutoloadGenerator integration: fixtures, mocked installers and generated-file comparison)"] +#[ignore = "setUp needs mocked Config/InstallationManager/InstalledRepositoryInterface and TestCase tmp-dir helpers; no mock infra exists"] fn test_class_map_autoloading_empty_dir_and_exact_file() { todo!() } #[test] -#[ignore = "not yet ported (AutoloadGenerator integration: fixtures, mocked installers and generated-file comparison)"] +#[ignore = "setUp needs mocked Config/InstallationManager/InstalledRepositoryInterface and TestCase tmp-dir helpers; no mock infra exists"] fn test_class_map_autoloading_authoritative_and_apcu() { todo!() } #[test] -#[ignore = "not yet ported (AutoloadGenerator integration: fixtures, mocked installers and generated-file comparison)"] +#[ignore = "setUp needs mocked Config/InstallationManager/InstalledRepositoryInterface and TestCase tmp-dir helpers; no mock infra exists"] fn test_class_map_autoloading_authoritative_and_apcu_prefix() { todo!() } #[test] -#[ignore = "not yet ported (AutoloadGenerator integration: fixtures, mocked installers and generated-file comparison)"] +#[ignore = "setUp needs mocked Config/InstallationManager/InstalledRepositoryInterface and TestCase tmp-dir helpers; no mock infra exists"] fn test_files_autoload_generation() { todo!() } #[test] -#[ignore = "not yet ported (AutoloadGenerator integration: fixtures, mocked installers and generated-file comparison)"] +#[ignore = "setUp needs mocked Config/InstallationManager/InstalledRepositoryInterface and TestCase tmp-dir helpers; no mock infra exists"] fn test_files_autoload_generation_remove_extra_entities_from_autoload_files() { todo!() } #[test] -#[ignore = "not yet ported (AutoloadGenerator integration: fixtures, mocked installers and generated-file comparison)"] +#[ignore = "setUp needs mocked Config/InstallationManager/InstalledRepositoryInterface and TestCase tmp-dir helpers; no mock infra exists"] fn test_files_autoload_order_by_dependencies() { todo!() } #[test] -#[ignore = "not yet ported (AutoloadGenerator integration: fixtures, mocked installers and generated-file comparison)"] +#[ignore = "setUp needs mocked Config/InstallationManager/InstalledRepositoryInterface and TestCase tmp-dir helpers; no mock infra exists"] fn test_override_vendors_autoloading() { todo!() } #[test] -#[ignore = "not yet ported (AutoloadGenerator integration: fixtures, mocked installers and generated-file comparison)"] +#[ignore = "setUp needs mocked Config/InstallationManager/InstalledRepositoryInterface and TestCase tmp-dir helpers; no mock infra exists"] fn test_include_path_file_generation() { todo!() } #[test] -#[ignore = "not yet ported (AutoloadGenerator integration: fixtures, mocked installers and generated-file comparison)"] +#[ignore = "setUp needs mocked Config/InstallationManager/InstalledRepositoryInterface and TestCase tmp-dir helpers; no mock infra exists"] fn test_include_paths_are_prepended_in_autoload_file() { todo!() } #[test] -#[ignore = "not yet ported (AutoloadGenerator integration: fixtures, mocked installers and generated-file comparison)"] +#[ignore = "setUp needs mocked Config/InstallationManager/InstalledRepositoryInterface and TestCase tmp-dir helpers; no mock infra exists"] fn test_include_paths_in_root_package() { todo!() } #[test] -#[ignore = "not yet ported (AutoloadGenerator integration: fixtures, mocked installers and generated-file comparison)"] +#[ignore = "setUp needs mocked Config/InstallationManager/InstalledRepositoryInterface and TestCase tmp-dir helpers; no mock infra exists"] fn test_include_path_file_without_paths_is_skipped() { todo!() } #[test] -#[ignore = "not yet ported (AutoloadGenerator integration: fixtures, mocked installers and generated-file comparison)"] +#[ignore = "setUp needs mocked Config/InstallationManager/InstalledRepositoryInterface plus EventDispatcher::dispatch expectations; no mock infra exists"] fn test_pre_and_post_events_are_dispatched_during_autoload_dump() { todo!() } #[test] -#[ignore = "not yet ported (AutoloadGenerator integration: fixtures, mocked installers and generated-file comparison)"] +#[ignore = "setUp needs mocked Config/InstallationManager/InstalledRepositoryInterface and TestCase tmp-dir helpers; no mock infra exists"] fn test_use_global_include_path() { todo!() } #[test] -#[ignore = "not yet ported (AutoloadGenerator integration: fixtures, mocked installers and generated-file comparison)"] +#[ignore = "setUp needs mocked Config/InstallationManager/InstalledRepositoryInterface and TestCase tmp-dir helpers; no mock infra exists"] fn test_vendor_dir_excluded_from_working_dir() { todo!() } #[test] -#[ignore = "not yet ported (AutoloadGenerator integration: fixtures, mocked installers and generated-file comparison)"] +#[ignore = "setUp needs mocked Config/InstallationManager/InstalledRepositoryInterface and TestCase tmp-dir helpers; no mock infra exists"] fn test_up_level_relative_paths() { todo!() } #[test] -#[ignore = "not yet ported (AutoloadGenerator integration: fixtures, mocked installers and generated-file comparison)"] +#[ignore = "setUp needs mocked Config/InstallationManager/InstalledRepositoryInterface and TestCase tmp-dir helpers; no mock infra exists"] fn test_autoload_rules_in_package_that_does_not_exist_on_disk() { todo!() } #[test] -#[ignore = "not yet ported (AutoloadGenerator integration: fixtures, mocked installers and generated-file comparison)"] +#[ignore = "setUp needs mocked Config/InstallationManager/InstalledRepositoryInterface and TestCase tmp-dir helpers; no mock infra exists"] fn test_empty_paths() { todo!() } #[test] -#[ignore = "not yet ported (AutoloadGenerator integration: fixtures, mocked installers and generated-file comparison)"] +#[ignore = "setUp needs mocked Config/InstallationManager/InstalledRepositoryInterface and TestCase tmp-dir helpers; no mock infra exists"] fn test_vendor_substring_path() { todo!() } #[test] -#[ignore = "not yet ported (AutoloadGenerator integration: fixtures, mocked installers and generated-file comparison)"] +#[ignore = "setUp needs mocked Config/InstallationManager/InstalledRepositoryInterface and TestCase tmp-dir helpers; no mock infra exists"] fn test_exclude_from_classmap() { todo!() } #[test] -#[ignore = "not yet ported (AutoloadGenerator integration: fixtures, mocked installers and generated-file comparison)"] +#[ignore = "setUp needs mocked Config/InstallationManager/InstalledRepositoryInterface and TestCase tmp-dir helpers; no mock infra exists"] fn test_generates_platform_check() { todo!() } #[test] -#[ignore = "not yet ported (AutoloadGenerator integration: fixtures, mocked installers and generated-file comparison)"] +#[ignore = "setUp needs mocked Config/InstallationManager/InstalledRepositoryInterface and TestCase tmp-dir helpers; no mock infra exists"] fn test_absolute_symlink_with_psr4_does_not_generate_warnings() { todo!() } #[test] -#[ignore = "not yet ported (AutoloadGenerator integration: fixtures, mocked installers and generated-file comparison)"] +#[ignore = "setUp needs mocked Config/InstallationManager/InstalledRepositoryInterface and TestCase tmp-dir helpers; no mock infra exists"] fn test_absolute_symlink_with_classmap_exclude_from_classmap() { todo!() } diff --git a/crates/shirabe/tests/autoload/class_loader_test.rs b/crates/shirabe/tests/autoload/class_loader_test.rs index 68a0ba4..66cc3e8 100644 --- a/crates/shirabe/tests/autoload/class_loader_test.rs +++ b/crates/shirabe/tests/autoload/class_loader_test.rs @@ -6,7 +6,7 @@ use shirabe::autoload::class_loader::ClassLoader; // class_exists(). There is no equivalent runtime notion of loading and defining a // class in Rust, so this case cannot be ported faithfully. #[test] -#[ignore = "relies on PHP loadClass()/class_exists() runtime class loading, which has no Rust equivalent"] +#[ignore = "depends on PHP runtime class_exists() to verify loadClass defined a class; no Rust equivalent"] fn test_load_class() { todo!() } @@ -20,7 +20,7 @@ fn test_get_prefixes_with_no_psr0_configuration() { // In PHP this serializes the loader and unserializes it, then compares every getter. // PHP serialize()/unserialize() has no Rust equivalent here. #[test] -#[ignore = "relies on PHP serialize()/unserialize() round-tripping of the ClassLoader, which is not ported"] +#[ignore = "depends on PHP serialize()/unserialize() round-trip of ClassLoader; no Rust equivalent"] fn test_serializability() { todo!() } diff --git a/crates/shirabe/tests/cache_test.rs b/crates/shirabe/tests/cache_test.rs index 1e346cf..2e18856 100644 --- a/crates/shirabe/tests/cache_test.rs +++ b/crates/shirabe/tests/cache_test.rs @@ -59,8 +59,8 @@ impl Drop for TearDown { // In PHP these mock Cache::getFinder() to feed the gc() routine a controlled set of // files. getFinder is pub(crate) and cannot be overridden from a test, so the // finder-driven removal paths cannot be exercised faithfully here. +#[ignore = "requires mocking Cache::get_finder (pub(crate), PHPUnit MockObject) to feed gc() a controlled Finder iterator; not overridable from a test"] #[test] -#[ignore = "mocks Cache::getFinder to drive gc(); getFinder cannot be overridden from a test"] fn test_remove_outdated_files() { let SetUp { root, files, cache } = set_up(); let _tear_down = TearDown::new(root.path().to_path_buf()); @@ -68,8 +68,8 @@ fn test_remove_outdated_files() { todo!() } +#[ignore = "requires mocking Cache::get_finder (pub(crate), PHPUnit MockObject) to feed gc() a controlled Finder iterator; not overridable from a test"] #[test] -#[ignore = "mocks Cache::getFinder to drive gc(); getFinder cannot be overridden from a test"] fn test_remove_files_when_cache_is_too_large() { let SetUp { root, files, cache } = set_up(); let _tear_down = TearDown::new(root.path().to_path_buf()); diff --git a/crates/shirabe/tests/command/about_command_test.rs b/crates/shirabe/tests/command/about_command_test.rs index 3f19885..591fbfa 100644 --- a/crates/shirabe/tests/command/about_command_test.rs +++ b/crates/shirabe/tests/command/about_command_test.rs @@ -1,7 +1,7 @@ //! ref: composer/tests/Composer/Test/Command/AboutCommandTest.php #[test] -#[ignore = "requires the ApplicationTester harness (TestCase::getApplicationTester), which is not yet ported"] +#[ignore = "missing get_application_tester (ApplicationTester) infrastructure and Application::get_display/set_auto_exit"] fn test_about() { todo!() } diff --git a/crates/shirabe/tests/command/archive_command_test.rs b/crates/shirabe/tests/command/archive_command_test.rs index b716a3c..b381129 100644 --- a/crates/shirabe/tests/command/archive_command_test.rs +++ b/crates/shirabe/tests/command/archive_command_test.rs @@ -1,19 +1,19 @@ //! ref: composer/tests/Composer/Test/Command/ArchiveCommandTest.php #[test] -#[ignore = "requires the ApplicationTester/initTempComposer harness, which is not yet ported"] +#[ignore = "requires PHPUnit getMockBuilder/onlyMethods partial mock of ArchiveCommand (override tryComposer/requireComposer) plus expects()/willReturn() mocks for OutputInterface/EventDispatcher/ArchiveManager/RootPackageInterface; no mocking infrastructure exists"] fn test_uses_config_from_composer_object() { todo!() } #[test] -#[ignore = "requires the ApplicationTester/initTempComposer harness, which is not yet ported"] +#[ignore = "requires PHPUnit getMockBuilder/onlyMethods partial mock of ArchiveCommand (override tryComposer/archive) with expects()/with()/willReturn() expectation verification and Factory::createConfig mock OutputInterface; no mocking infrastructure exists"] fn test_uses_config_from_factory_when_composer_is_not_defined() { todo!() } #[test] -#[ignore = "requires the ApplicationTester/initTempComposer harness, which is not yet ported"] +#[ignore = "requires PHPUnit getMockBuilder/onlyMethods partial mock of ArchiveCommand plus expects()/willReturn() mocks for OutputInterface/EventDispatcher/ArchiveManager/InstalledRepositoryInterface/RepositoryManager; no mocking infrastructure exists"] fn test_uses_config_from_composer_object_with_package_name() { todo!() } diff --git a/crates/shirabe/tests/command/audit_command_test.rs b/crates/shirabe/tests/command/audit_command_test.rs index 4972793..7d585d9 100644 --- a/crates/shirabe/tests/command/audit_command_test.rs +++ b/crates/shirabe/tests/command/audit_command_test.rs @@ -1,25 +1,25 @@ //! ref: composer/tests/Composer/Test/Command/AuditCommandTest.php #[test] -#[ignore = "requires the ApplicationTester/initTempComposer harness, which is not yet ported"] +#[ignore = "missing TestCase::init_temp_composer and get_application_tester (ApplicationTester) infrastructure"] fn test_successful_response_code_when_no_packages_are_required() { todo!() } #[test] -#[ignore = "requires the ApplicationTester/initTempComposer harness, which is not yet ported"] +#[ignore = "missing TestCase::init_temp_composer, create_installed_json, and get_application_tester (ApplicationTester) infrastructure"] fn test_error_auditing_lock_file_when_it_is_missing() { todo!() } #[test] -#[ignore = "requires the ApplicationTester/initTempComposer harness, which is not yet ported"] +#[ignore = "missing TestCase::init_temp_composer, create_installed_json, create_composer_lock, and get_application_tester (ApplicationTester) infrastructure"] fn test_audit_package_with_no_security_vulnerabilities() { todo!() } #[test] -#[ignore = "requires the ApplicationTester/initTempComposer harness, which is not yet ported"] +#[ignore = "missing TestCase::init_temp_composer, create_installed_json, create_composer_lock, and get_application_tester (ApplicationTester) infrastructure"] fn test_audit_package_with_no_dev_option_passed() { todo!() } diff --git a/crates/shirabe/tests/command/base_dependency_command_test.rs b/crates/shirabe/tests/command/base_dependency_command_test.rs index 74a8adf..a112933 100644 --- a/crates/shirabe/tests/command/base_dependency_command_test.rs +++ b/crates/shirabe/tests/command/base_dependency_command_test.rs @@ -1,43 +1,43 @@ //! ref: composer/tests/Composer/Test/Command/BaseDependencyCommandTest.php #[test] -#[ignore = "requires the ApplicationTester/initTempComposer harness, which is not yet ported"] +#[ignore = "ApplicationTester / get_application_tester helper not implemented"] fn test_exception_when_no_required_parameters() { todo!() } #[test] -#[ignore = "requires the ApplicationTester/initTempComposer harness, which is not yet ported"] +#[ignore = "init_temp_composer / get_application_tester helper not implemented"] fn test_exception_when_running_locked_without_lock_file() { todo!() } #[test] -#[ignore = "requires the ApplicationTester/initTempComposer harness, which is not yet ported"] +#[ignore = "init_temp_composer / get_application_tester helper not implemented"] fn test_exception_when_it_could_not_found_the_package() { todo!() } #[test] -#[ignore = "requires the ApplicationTester/initTempComposer harness, which is not yet ported"] +#[ignore = "init_temp_composer / create_installed_json / create_composer_lock / get_application_tester helpers not implemented"] fn test_exception_when_package_was_not_found_in_project() { todo!() } #[test] -#[ignore = "requires the ApplicationTester/initTempComposer harness, which is not yet ported"] +#[ignore = "init_temp_composer / create_composer_lock / get_application_tester helpers not implemented"] fn test_warning_when_dependencies_are_not_installed() { todo!() } #[test] -#[ignore = "requires the ApplicationTester/initTempComposer harness, which is not yet ported"] +#[ignore = "init_temp_composer / create_composer_lock / create_installed_json / get_application_tester helpers not implemented"] fn test_why_command_outputs() { todo!() } #[test] -#[ignore = "requires the ApplicationTester/initTempComposer harness, which is not yet ported"] +#[ignore = "init_temp_composer / create_composer_lock / create_installed_json / get_application_tester helpers not implemented"] fn test_why_not_command_outputs() { todo!() } diff --git a/crates/shirabe/tests/command/bump_command_test.rs b/crates/shirabe/tests/command/bump_command_test.rs index a96dcc7..4e9a49c 100644 --- a/crates/shirabe/tests/command/bump_command_test.rs +++ b/crates/shirabe/tests/command/bump_command_test.rs @@ -1,19 +1,19 @@ //! ref: composer/tests/Composer/Test/Command/BumpCommandTest.php +#[ignore = "missing TestCase::init_temp_composer, create_installed_json, create_composer_lock, and get_application_tester (ApplicationTester) infrastructure"] #[test] -#[ignore = "requires the ApplicationTester/initTempComposer harness, which is not yet ported"] fn test_bump() { todo!() } +#[ignore = "missing TestCase::init_temp_composer and get_application_tester (ApplicationTester) infrastructure"] #[test] -#[ignore = "requires the ApplicationTester/initTempComposer harness, which is not yet ported"] fn test_bump_fails_on_non_existing_composer_file() { todo!() } +#[ignore = "missing TestCase::init_temp_composer and get_application_tester (ApplicationTester) infrastructure"] #[test] -#[ignore = "requires the ApplicationTester/initTempComposer harness, which is not yet ported"] fn test_bump_fails_on_write_error_to_composer_file() { todo!() } diff --git a/crates/shirabe/tests/command/check_platform_reqs_command_test.rs b/crates/shirabe/tests/command/check_platform_reqs_command_test.rs index a3a6ae5..9cbba58 100644 --- a/crates/shirabe/tests/command/check_platform_reqs_command_test.rs +++ b/crates/shirabe/tests/command/check_platform_reqs_command_test.rs @@ -1,19 +1,19 @@ //! ref: composer/tests/Composer/Test/Command/CheckPlatformReqsCommandTest.php #[test] -#[ignore = "requires the ApplicationTester/initTempComposer harness, which is not yet ported"] +#[ignore = "missing TestCase::init_temp_composer, create_installed_json, create_composer_lock and get_application_tester (ApplicationTester) infrastructure"] fn test_platform_reqs_are_satisfied() { todo!() } #[test] -#[ignore = "requires the ApplicationTester/initTempComposer harness, which is not yet ported"] +#[ignore = "missing TestCase::init_temp_composer and get_application_tester (ApplicationTester) infrastructure"] fn test_exception_thrown_if_no_lockfile_found() { todo!() } #[test] -#[ignore = "requires the ApplicationTester/initTempComposer harness, which is not yet ported"] +#[ignore = "missing TestCase::init_temp_composer, create_installed_json, create_composer_lock and get_application_tester (ApplicationTester) infrastructure"] fn test_failed_platform_requirement() { todo!() } diff --git a/crates/shirabe/tests/command/clear_cache_command_test.rs b/crates/shirabe/tests/command/clear_cache_command_test.rs index 29a745b..4ad04dd 100644 --- a/crates/shirabe/tests/command/clear_cache_command_test.rs +++ b/crates/shirabe/tests/command/clear_cache_command_test.rs @@ -16,7 +16,7 @@ impl Drop for TearDown { } #[test] -#[ignore = "requires the ApplicationTester/initTempComposer harness, which is not yet ported"] +#[ignore = "ApplicationTester / get_application_tester helper not implemented"] fn test_clear_cache_command_success() { let _tear_down = TearDown; @@ -24,7 +24,7 @@ fn test_clear_cache_command_success() { } #[test] -#[ignore = "requires the ApplicationTester/initTempComposer harness, which is not yet ported"] +#[ignore = "ApplicationTester / get_application_tester helper not implemented"] fn test_clear_cache_command_with_option_garbage_collection() { let _tear_down = TearDown; @@ -32,7 +32,7 @@ fn test_clear_cache_command_with_option_garbage_collection() { } #[test] -#[ignore = "requires the ApplicationTester/initTempComposer harness, which is not yet ported"] +#[ignore = "ApplicationTester / get_application_tester helper not implemented"] fn test_clear_cache_command_with_option_no_cache() { let _tear_down = TearDown; diff --git a/crates/shirabe/tests/command/config_command_test.rs b/crates/shirabe/tests/command/config_command_test.rs index 63bcac7..70e6a97 100644 --- a/crates/shirabe/tests/command/config_command_test.rs +++ b/crates/shirabe/tests/command/config_command_test.rs @@ -1,31 +1,31 @@ //! ref: composer/tests/Composer/Test/Command/ConfigCommandTest.php #[test] -#[ignore = "requires the ApplicationTester/initTempComposer harness, which is not yet ported"] +#[ignore = "requires ApplicationTester and TestCase::initTempComposer harness (not implemented)"] fn test_config_updates() { todo!() } #[test] -#[ignore = "requires the ApplicationTester/initTempComposer harness, which is not yet ported"] +#[ignore = "requires ApplicationTester and TestCase::initTempComposer harness (not implemented)"] fn test_config_reads() { todo!() } #[test] -#[ignore = "requires the ApplicationTester/initTempComposer harness, which is not yet ported"] +#[ignore = "requires ApplicationTester (getApplicationTester) harness (not implemented)"] fn test_config_throws_for_invalid_arg_combination() { todo!() } #[test] -#[ignore = "requires the ApplicationTester/initTempComposer harness, which is not yet ported"] +#[ignore = "requires ApplicationTester and TestCase::initTempComposer harness (not implemented)"] fn test_config_throws_for_invalid_severity() { todo!() } #[test] -#[ignore = "requires the ApplicationTester/initTempComposer harness, which is not yet ported"] +#[ignore = "requires ApplicationTester and TestCase::initTempComposer harness (not implemented)"] fn test_config_throws_when_merging_array_with_object() { todo!() } diff --git a/crates/shirabe/tests/command/diagnose_command_test.rs b/crates/shirabe/tests/command/diagnose_command_test.rs index 4b5dc9a..a5a6c8b 100644 --- a/crates/shirabe/tests/command/diagnose_command_test.rs +++ b/crates/shirabe/tests/command/diagnose_command_test.rs @@ -1,13 +1,13 @@ //! ref: composer/tests/Composer/Test/Command/DiagnoseCommandTest.php #[test] -#[ignore = "requires the ApplicationTester/initTempComposer harness, which is not yet ported"] +#[ignore = "missing TestCase::init_temp_composer and get_application_tester (ApplicationTester) infrastructure"] fn test_cmd_fail() { todo!() } #[test] -#[ignore = "requires the ApplicationTester/initTempComposer harness, which is not yet ported"] +#[ignore = "missing TestCase::init_temp_composer and get_application_tester (ApplicationTester) infrastructure"] fn test_cmd_success() { todo!() } diff --git a/crates/shirabe/tests/command/dump_autoload_command_test.rs b/crates/shirabe/tests/command/dump_autoload_command_test.rs index db27ebb..b3e79b3 100644 --- a/crates/shirabe/tests/command/dump_autoload_command_test.rs +++ b/crates/shirabe/tests/command/dump_autoload_command_test.rs @@ -1,73 +1,73 @@ //! ref: composer/tests/Composer/Test/Command/DumpAutoloadCommandTest.php #[test] -#[ignore = "requires the ApplicationTester/initTempComposer harness, which is not yet ported"] +#[ignore = "ApplicationTester / get_application_tester helper not implemented"] fn test_dump_autoload() { todo!() } #[test] -#[ignore = "requires the ApplicationTester/initTempComposer harness, which is not yet ported"] +#[ignore = "ApplicationTester / get_application_tester helper not implemented"] fn test_dump_dev_autoload() { todo!() } #[test] -#[ignore = "requires the ApplicationTester/initTempComposer harness, which is not yet ported"] +#[ignore = "ApplicationTester / get_application_tester helper not implemented"] fn test_dump_no_dev_autoload() { todo!() } #[test] -#[ignore = "requires the ApplicationTester/initTempComposer harness, which is not yet ported"] +#[ignore = "ApplicationTester / get_application_tester helper not implemented"] fn test_using_optimize_and_strict_psr() { todo!() } #[test] -#[ignore = "requires the ApplicationTester/initTempComposer harness, which is not yet ported"] +#[ignore = "init_temp_composer / get_application_tester helpers not implemented"] fn test_fails_using_strict_psr_if_class_map_violations_are_found() { todo!() } #[test] -#[ignore = "requires the ApplicationTester/initTempComposer harness, which is not yet ported"] +#[ignore = "ApplicationTester / get_application_tester helper not implemented"] fn test_using_classmap_authoritative() { todo!() } #[test] -#[ignore = "requires the ApplicationTester/initTempComposer harness, which is not yet ported"] +#[ignore = "ApplicationTester / get_application_tester helper not implemented"] fn test_using_classmap_authoritative_and_strict_psr() { todo!() } #[test] -#[ignore = "requires the ApplicationTester/initTempComposer harness, which is not yet ported"] +#[ignore = "ApplicationTester / get_application_tester helper not implemented"] fn test_strict_psr_does_not_work_without_optimized_autoloader() { todo!() } #[test] -#[ignore = "requires the ApplicationTester/initTempComposer harness, which is not yet ported"] +#[ignore = "ApplicationTester / get_application_tester helper not implemented"] fn test_dev_and_no_dev_cannot_be_combined() { todo!() } #[test] -#[ignore = "requires the ApplicationTester/initTempComposer harness, which is not yet ported"] +#[ignore = "init_temp_composer / get_application_tester helpers not implemented"] fn test_with_custom_autoloader_suffix() { todo!() } #[test] -#[ignore = "requires the ApplicationTester/initTempComposer harness, which is not yet ported"] +#[ignore = "init_temp_composer / get_application_tester helpers not implemented"] fn test_with_existing_composer_lock_and_autoloader_suffix() { todo!() } #[test] -#[ignore = "requires the ApplicationTester/initTempComposer harness, which is not yet ported"] +#[ignore = "init_temp_composer / get_application_tester helpers not implemented"] fn test_with_existing_composer_lock_without_autoloader_suffix() { todo!() } diff --git a/crates/shirabe/tests/command/exec_command_test.rs b/crates/shirabe/tests/command/exec_command_test.rs index 89963db..d182754 100644 --- a/crates/shirabe/tests/command/exec_command_test.rs +++ b/crates/shirabe/tests/command/exec_command_test.rs @@ -1,13 +1,13 @@ //! ref: composer/tests/Composer/Test/Command/ExecCommandTest.php #[test] -#[ignore = "requires the ApplicationTester/initTempComposer harness, which is not yet ported"] +#[ignore = "missing TestCase::init_temp_composer and get_application_tester (ApplicationTester with run/get_display) infrastructure"] fn test_list_throws_if_no_binaries_exist() { todo!() } #[test] -#[ignore = "requires the ApplicationTester/initTempComposer harness, which is not yet ported"] +#[ignore = "missing TestCase::init_temp_composer and get_application_tester (ApplicationTester with run/get_display) infrastructure"] fn test_list() { todo!() } diff --git a/crates/shirabe/tests/command/fund_command_test.rs b/crates/shirabe/tests/command/fund_command_test.rs index d66795f..f75090c 100644 --- a/crates/shirabe/tests/command/fund_command_test.rs +++ b/crates/shirabe/tests/command/fund_command_test.rs @@ -1,7 +1,7 @@ //! ref: composer/tests/Composer/Test/Command/FundCommandTest.php +#[ignore = "missing TestCase::init_temp_composer, create_installed_json, and get_application_tester (ApplicationTester) infrastructure"] #[test] -#[ignore = "requires the ApplicationTester/initTempComposer harness, which is not yet ported"] fn test_fund_command() { todo!() } diff --git a/crates/shirabe/tests/command/global_command_test.rs b/crates/shirabe/tests/command/global_command_test.rs index 10ddf6c..0b62150 100644 --- a/crates/shirabe/tests/command/global_command_test.rs +++ b/crates/shirabe/tests/command/global_command_test.rs @@ -16,7 +16,7 @@ impl Drop for TearDown { } #[test] -#[ignore = "requires the ApplicationTester/initTempComposer harness, which is not yet ported"] +#[ignore = "requires ApplicationTester and TestCase::initTempComposer/getUniqueTmpDirectory harness (not implemented)"] fn test_global() { let _tear_down = TearDown; @@ -24,7 +24,7 @@ fn test_global() { } #[test] -#[ignore = "requires the ApplicationTester/initTempComposer harness, which is not yet ported"] +#[ignore = "requires ApplicationTester and TestCase::getUniqueTmpDirectory harness (not implemented)"] fn test_cannot_create_home() { let _tear_down = TearDown; @@ -32,7 +32,7 @@ fn test_cannot_create_home() { } #[test] -#[ignore = "requires the ApplicationTester/initTempComposer harness, which is not yet ported"] +#[ignore = "requires ApplicationTester and TestCase::initTempComposer/createInstalledJson harness (not implemented)"] fn test_global_show() { let _tear_down = TearDown; @@ -40,7 +40,7 @@ fn test_global_show() { } #[test] -#[ignore = "requires the ApplicationTester/initTempComposer harness, which is not yet ported"] +#[ignore = "requires ApplicationTester and TestCase::initTempComposer/createInstalledJson harness (not implemented)"] fn test_global_show_without_packages() { let _tear_down = TearDown; @@ -48,7 +48,7 @@ fn test_global_show_without_packages() { } #[test] -#[ignore = "requires the ApplicationTester/initTempComposer harness, which is not yet ported"] +#[ignore = "requires ApplicationTester and TestCase::initTempComposer harness (not implemented)"] fn test_global_require() { let _tear_down = TearDown; @@ -56,7 +56,7 @@ fn test_global_require() { } #[test] -#[ignore = "requires the ApplicationTester/initTempComposer harness, which is not yet ported"] +#[ignore = "requires ApplicationTester and TestCase::initTempComposer/createInstalledJson/createComposerLock harness (not implemented)"] fn test_global_update() { let _tear_down = TearDown; @@ -64,7 +64,7 @@ fn test_global_update() { } #[test] -#[ignore = "requires the ApplicationTester/initTempComposer harness, which is not yet ported"] +#[ignore = "requires ApplicationTester and TestCase::initTempComposer harness (not implemented)"] fn test_global_changes_directory() { let _tear_down = TearDown; @@ -72,7 +72,7 @@ fn test_global_changes_directory() { } #[test] -#[ignore = "requires the ApplicationTester/initTempComposer harness, which is not yet ported"] +#[ignore = "requires ApplicationTester and TestCase::initTempComposer harness (not implemented)"] fn test_global_missing_command_name() { let _tear_down = TearDown; diff --git a/crates/shirabe/tests/command/home_command_test.rs b/crates/shirabe/tests/command/home_command_test.rs index 0bb4357..b8b67d7 100644 --- a/crates/shirabe/tests/command/home_command_test.rs +++ b/crates/shirabe/tests/command/home_command_test.rs @@ -1,7 +1,7 @@ //! ref: composer/tests/Composer/Test/Command/HomeCommandTest.php #[test] -#[ignore = "requires the ApplicationTester/initTempComposer harness, which is not yet ported"] +#[ignore = "init_temp_composer / create_installed_json / get_application_tester (ApplicationTester) helpers not implemented"] fn test_home_command_with_show_flag() { todo!() } diff --git a/crates/shirabe/tests/command/init_command_test.rs b/crates/shirabe/tests/command/init_command_test.rs index 043011d..7c296a0 100644 --- a/crates/shirabe/tests/command/init_command_test.rs +++ b/crates/shirabe/tests/command/init_command_test.rs @@ -3,6 +3,7 @@ // The author/namespace/git-config helpers are protected methods exercised via reflection // in PHP; the run cases need the ApplicationTester. Neither is available here. +use shirabe::command::init_command::InitCommand; use shirabe_php_shim::server_set; fn set_up() { @@ -10,112 +11,121 @@ fn set_up() { server_set("COMPOSER_DEFAULT_EMAIL", "john@example.com".to_string()); } +#[ignore = "InitCommand::parse_author_string is private; integration tests cannot reach it (PHP uses reflection)"] #[test] -#[ignore = "needs the ApplicationTester harness or reflection into protected InitCommand helpers"] fn test_parse_valid_author_string() { set_up(); todo!() } +#[ignore = "InitCommand::parse_author_string is private; integration tests cannot reach it (PHP uses reflection)"] #[test] -#[ignore = "needs the ApplicationTester harness or reflection into protected InitCommand helpers"] fn test_parse_empty_author_string() { set_up(); todo!() } +#[ignore = "InitCommand::parse_author_string is private; integration tests cannot reach it (PHP uses reflection)"] #[test] -#[ignore = "needs the ApplicationTester harness or reflection into protected InitCommand helpers"] fn test_parse_author_string_with_invalid_email() { set_up(); todo!() } +#[ignore] #[test] -#[ignore = "needs the ApplicationTester harness or reflection into protected InitCommand helpers"] fn test_namespace_from_valid_package_name() { set_up(); - todo!() + let command = InitCommand::new(); + let namespace = command.namespace_from_package_name("new_projects.acme-extra/package-name"); + assert_eq!( + Some("NewProjectsAcmeExtra\\PackageName".to_string()), + namespace + ); } +#[ignore] #[test] -#[ignore = "needs the ApplicationTester harness or reflection into protected InitCommand helpers"] fn test_namespace_from_invalid_package_name() { set_up(); - todo!() + let command = InitCommand::new(); + let namespace = command.namespace_from_package_name("invalid-package-name"); + assert_eq!(None, namespace); } +#[ignore] #[test] -#[ignore = "needs the ApplicationTester harness or reflection into protected InitCommand helpers"] fn test_namespace_from_missing_package_name() { set_up(); - todo!() + let command = InitCommand::new(); + let namespace = command.namespace_from_package_name(""); + assert_eq!(None, namespace); } +#[ignore = "needs TestCase::init_temp_composer and get_application_tester (ApplicationTester) infrastructure, not implemented"] #[test] -#[ignore = "needs the ApplicationTester harness or reflection into protected InitCommand helpers"] fn test_run_command() { set_up(); todo!() } +#[ignore = "needs TestCase::init_temp_composer and get_application_tester (ApplicationTester) infrastructure, not implemented"] #[test] -#[ignore = "needs the ApplicationTester harness or reflection into protected InitCommand helpers"] fn test_run_command_invalid() { set_up(); todo!() } +#[ignore = "needs TestCase::init_temp_composer and get_application_tester (ApplicationTester) infrastructure, not implemented"] #[test] -#[ignore = "needs the ApplicationTester harness or reflection into protected InitCommand helpers"] fn test_run_guess_name_from_dir_sanitizes_dir() { set_up(); todo!() } +#[ignore = "needs TestCase::init_temp_composer and get_application_tester (ApplicationTester with set_inputs) infrastructure, not implemented"] #[test] -#[ignore = "needs the ApplicationTester harness or reflection into protected InitCommand helpers"] fn test_interactive_run() { set_up(); todo!() } +#[ignore = "InitCommand::format_authors is pub(crate); integration tests cannot reach it (PHP subclasses via DummyInitCommand)"] #[test] -#[ignore = "needs the ApplicationTester harness or reflection into protected InitCommand helpers"] fn test_format_authors() { set_up(); todo!() } +#[ignore = "InitCommand::get_git_config is pub(crate); integration tests cannot reach it (PHP subclasses via DummyInitCommand)"] #[test] -#[ignore = "needs the ApplicationTester harness or reflection into protected InitCommand helpers"] fn test_get_git_config() { set_up(); todo!() } +#[ignore = "InitCommand::add_vendor_ignore is pub(crate) and test needs TestCase::get_unique_tmp_directory; neither reachable from integration tests"] #[test] -#[ignore = "needs the ApplicationTester harness or reflection into protected InitCommand helpers"] fn test_add_vendor_ignore() { set_up(); todo!() } +#[ignore = "InitCommand::has_vendor_ignore/add_vendor_ignore are pub(crate) and test needs TestCase::get_unique_tmp_directory; neither reachable from integration tests"] #[test] -#[ignore = "needs the ApplicationTester harness or reflection into protected InitCommand helpers"] fn test_has_vendor_ignore() { set_up(); diff --git a/crates/shirabe/tests/command/install_command_test.rs b/crates/shirabe/tests/command/install_command_test.rs index 1774721..a0586ab 100644 --- a/crates/shirabe/tests/command/install_command_test.rs +++ b/crates/shirabe/tests/command/install_command_test.rs @@ -1,25 +1,25 @@ //! ref: composer/tests/Composer/Test/Command/InstallCommandTest.php #[test] -#[ignore = "requires the ApplicationTester/initTempComposer harness, which is not yet ported"] +#[ignore = "requires ApplicationTester and TestCase::initTempComposer/createComposerLock/createInstalledJson harness (not implemented)"] fn test_install_command_errors() { todo!() } #[test] -#[ignore = "requires the ApplicationTester/initTempComposer harness, which is not yet ported"] +#[ignore = "requires ApplicationTester and TestCase::initTempComposer/createComposerLock harness (not implemented)"] fn test_install_from_empty_vendor() { todo!() } #[test] -#[ignore = "requires the ApplicationTester/initTempComposer harness, which is not yet ported"] +#[ignore = "requires ApplicationTester and TestCase::initTempComposer/createComposerLock harness (not implemented)"] fn test_install_from_empty_vendor_no_dev() { todo!() } #[test] -#[ignore = "requires the ApplicationTester/initTempComposer harness, which is not yet ported"] +#[ignore = "requires ApplicationTester and TestCase::initTempComposer/createComposerLock/createInstalledJson harness (not implemented)"] fn test_install_new_packages_with_existing_partial_vendor() { todo!() } diff --git a/crates/shirabe/tests/command/licenses_command_test.rs b/crates/shirabe/tests/command/licenses_command_test.rs index 113a77e..6bfe832 100644 --- a/crates/shirabe/tests/command/licenses_command_test.rs +++ b/crates/shirabe/tests/command/licenses_command_test.rs @@ -7,7 +7,7 @@ fn set_up() { } #[test] -#[ignore = "requires the ApplicationTester/initTempComposer harness, which is not yet ported"] +#[ignore = "missing TestCase::init_temp_composer / create_installed_json / create_composer_lock and get_application_tester (ApplicationTester) infrastructure"] fn test_basic_run() { set_up(); @@ -15,7 +15,7 @@ fn test_basic_run() { } #[test] -#[ignore = "requires the ApplicationTester/initTempComposer harness, which is not yet ported"] +#[ignore = "missing TestCase::init_temp_composer / create_installed_json / create_composer_lock and get_application_tester (ApplicationTester) infrastructure"] fn test_no_dev() { set_up(); @@ -23,7 +23,7 @@ fn test_no_dev() { } #[test] -#[ignore = "requires the ApplicationTester/initTempComposer harness, which is not yet ported"] +#[ignore = "missing TestCase::init_temp_composer / create_installed_json / create_composer_lock and get_application_tester (ApplicationTester) infrastructure"] fn test_format_json() { set_up(); @@ -31,7 +31,7 @@ fn test_format_json() { } #[test] -#[ignore = "requires the ApplicationTester/initTempComposer harness, which is not yet ported"] +#[ignore = "missing TestCase::init_temp_composer / create_installed_json / create_composer_lock and get_application_tester (ApplicationTester) infrastructure"] fn test_format_summary() { set_up(); @@ -39,7 +39,7 @@ fn test_format_summary() { } #[test] -#[ignore = "requires the ApplicationTester/initTempComposer harness, which is not yet ported"] +#[ignore = "missing TestCase::init_temp_composer / create_installed_json / create_composer_lock and get_application_tester (ApplicationTester) infrastructure"] fn test_format_unknown() { set_up(); @@ -47,7 +47,7 @@ fn test_format_unknown() { } #[test] -#[ignore = "requires the ApplicationTester/initTempComposer harness, which is not yet ported"] +#[ignore = "missing TestCase::init_temp_composer / create_installed_json / create_composer_lock and get_application_tester (ApplicationTester) infrastructure"] fn test_locked() { set_up(); @@ -55,7 +55,7 @@ fn test_locked() { } #[test] -#[ignore = "requires the ApplicationTester/initTempComposer harness, which is not yet ported"] +#[ignore = "missing TestCase::init_temp_composer / create_installed_json / create_composer_lock and get_application_tester (ApplicationTester) infrastructure"] fn test_locked_no_dev() { set_up(); @@ -63,7 +63,7 @@ fn test_locked_no_dev() { } #[test] -#[ignore = "requires the ApplicationTester/initTempComposer harness, which is not yet ported"] +#[ignore = "missing TestCase::init_temp_composer / create_installed_json / create_composer_lock and get_application_tester (ApplicationTester) infrastructure"] fn test_locked_without_lock_file() { set_up(); diff --git a/crates/shirabe/tests/command/reinstall_command_test.rs b/crates/shirabe/tests/command/reinstall_command_test.rs index df0b618..b420fd5 100644 --- a/crates/shirabe/tests/command/reinstall_command_test.rs +++ b/crates/shirabe/tests/command/reinstall_command_test.rs @@ -1,7 +1,7 @@ //! ref: composer/tests/Composer/Test/Command/ReinstallCommandTest.php +#[ignore = "missing TestCase::init_temp_composer, create_composer_lock, create_installed_json, and get_application_tester (ApplicationTester) infrastructure"] #[test] -#[ignore = "requires the ApplicationTester/initTempComposer harness, which is not yet ported"] fn test_reinstall_command() { todo!() } diff --git a/crates/shirabe/tests/command/remove_command_test.rs b/crates/shirabe/tests/command/remove_command_test.rs index 297d136..2ea8c88 100644 --- a/crates/shirabe/tests/command/remove_command_test.rs +++ b/crates/shirabe/tests/command/remove_command_test.rs @@ -1,97 +1,97 @@ //! ref: composer/tests/Composer/Test/Command/RemoveCommandTest.php +#[ignore = "missing TestCase::get_application_tester (ApplicationTester) infrastructure"] #[test] -#[ignore = "requires the ApplicationTester/initTempComposer harness, which is not yet ported"] fn test_exception_running_with_no_remove_packages() { todo!() } +#[ignore = "missing TestCase::init_temp_composer and get_application_tester (ApplicationTester) infrastructure"] #[test] -#[ignore = "requires the ApplicationTester/initTempComposer harness, which is not yet ported"] fn test_exception_when_running_unused_without_lock_file() { todo!() } +#[ignore = "missing TestCase::init_temp_composer, create_installed_json, and get_application_tester (ApplicationTester) infrastructure"] #[test] -#[ignore = "requires the ApplicationTester/initTempComposer harness, which is not yet ported"] fn test_warning_when_removing_non_existent_package() { todo!() } +#[ignore = "missing TestCase::init_temp_composer and get_application_tester (ApplicationTester) infrastructure"] #[test] -#[ignore = "requires the ApplicationTester/initTempComposer harness, which is not yet ported"] fn test_warning_when_removing_package_from_wrong_type() { todo!() } +#[ignore = "missing TestCase::init_temp_composer and get_application_tester (ApplicationTester) infrastructure"] #[test] -#[ignore = "requires the ApplicationTester/initTempComposer harness, which is not yet ported"] fn test_warning_when_removing_package_with_deprecated_dependencies_flag() { todo!() } +#[ignore = "missing TestCase::init_temp_composer, create_installed_json, create_composer_lock, and get_application_tester (ApplicationTester) infrastructure"] #[test] -#[ignore = "requires the ApplicationTester/initTempComposer harness, which is not yet ported"] fn test_message_output_when_no_unused_packages_to_remove() { todo!() } +#[ignore = "missing TestCase::init_temp_composer, create_installed_json, create_composer_lock, and get_application_tester (ApplicationTester) infrastructure"] #[test] -#[ignore = "requires the ApplicationTester/initTempComposer harness, which is not yet ported"] fn test_remove_unused_package() { todo!() } +#[ignore = "missing TestCase::init_temp_composer, create_installed_json, create_composer_lock, and get_application_tester (ApplicationTester) infrastructure"] #[test] -#[ignore = "requires the ApplicationTester/initTempComposer harness, which is not yet ported"] fn test_remove_package_by_name() { todo!() } +#[ignore = "missing TestCase::init_temp_composer, create_installed_json, create_composer_lock, and get_application_tester (ApplicationTester) infrastructure"] #[test] -#[ignore = "requires the ApplicationTester/initTempComposer harness, which is not yet ported"] fn test_remove_package_by_name_with_dry_run() { todo!() } +#[ignore = "missing TestCase::init_temp_composer, create_installed_json, create_composer_lock, and get_application_tester (ApplicationTester) infrastructure"] #[test] -#[ignore = "requires the ApplicationTester/initTempComposer harness, which is not yet ported"] fn test_remove_allowed_plugin_package_with_no_other_allowed_plugins() { todo!() } +#[ignore = "missing TestCase::init_temp_composer and get_application_tester (ApplicationTester) infrastructure"] #[test] -#[ignore = "requires the ApplicationTester/initTempComposer harness, which is not yet ported"] fn test_remove_allowed_plugin_package_with_other_allowed_plugins() { todo!() } +#[ignore = "missing TestCase::init_temp_composer, create_installed_json, create_composer_lock, and get_application_tester (ApplicationTester) infrastructure"] #[test] -#[ignore = "requires the ApplicationTester/initTempComposer harness, which is not yet ported"] fn test_remove_packages_by_vendor() { todo!() } +#[ignore = "missing TestCase::init_temp_composer, create_installed_json, create_composer_lock, and get_application_tester (ApplicationTester) infrastructure"] #[test] -#[ignore = "requires the ApplicationTester/initTempComposer harness, which is not yet ported"] fn test_remove_packages_by_vendor_with_dry_run() { todo!() } +#[ignore = "missing TestCase::init_temp_composer and get_application_tester (ApplicationTester) infrastructure"] #[test] -#[ignore = "requires the ApplicationTester/initTempComposer harness, which is not yet ported"] fn test_warning_when_removing_packages_by_vendor_from_wrong_type() { todo!() } +#[ignore = "missing TestCase::init_temp_composer, create_installed_json, create_composer_lock, and get_application_tester (ApplicationTester) infrastructure"] #[test] -#[ignore = "requires the ApplicationTester/initTempComposer harness, which is not yet ported"] fn test_package_still_present_error_when_no_install_flag_used() { todo!() } +#[ignore = "missing TestCase::init_temp_composer, create_installed_json, create_composer_lock, and get_application_tester (ApplicationTester) infrastructure"] #[test] -#[ignore = "requires the ApplicationTester/initTempComposer harness, which is not yet ported"] fn test_update_inherited_dependencies_flag_is_passed_to_post_remove_installer() { todo!() } diff --git a/crates/shirabe/tests/command/repository_command_test.rs b/crates/shirabe/tests/command/repository_command_test.rs index 33a4451..dd7a4bf 100644 --- a/crates/shirabe/tests/command/repository_command_test.rs +++ b/crates/shirabe/tests/command/repository_command_test.rs @@ -1,97 +1,97 @@ //! ref: composer/tests/Composer/Test/Command/RepositoryCommandTest.php #[test] -#[ignore = "requires the ApplicationTester/initTempComposer harness, which is not yet ported"] +#[ignore = "missing init_temp_composer and get_application_tester (ApplicationTester) test infrastructure"] fn test_list_with_no_repositories() { todo!() } #[test] -#[ignore = "requires the ApplicationTester/initTempComposer harness, which is not yet ported"] +#[ignore = "missing init_temp_composer and get_application_tester (ApplicationTester) test infrastructure"] fn test_list_with_repositories_as_list() { todo!() } #[test] -#[ignore = "requires the ApplicationTester/initTempComposer harness, which is not yet ported"] +#[ignore = "missing init_temp_composer and get_application_tester (ApplicationTester) test infrastructure"] fn test_list_with_repositories_as_assoc() { todo!() } #[test] -#[ignore = "requires the ApplicationTester/initTempComposer harness, which is not yet ported"] +#[ignore = "missing init_temp_composer and get_application_tester (ApplicationTester) test infrastructure"] fn test_add_repository_with_type_and_url() { todo!() } #[test] -#[ignore = "requires the ApplicationTester/initTempComposer harness, which is not yet ported"] +#[ignore = "missing init_temp_composer and get_application_tester (ApplicationTester) test infrastructure"] fn test_add_repository_with_json() { todo!() } #[test] -#[ignore = "requires the ApplicationTester/initTempComposer harness, which is not yet ported"] +#[ignore = "missing init_temp_composer and get_application_tester (ApplicationTester) test infrastructure"] fn test_remove_repository() { todo!() } #[test] -#[ignore = "requires the ApplicationTester/initTempComposer harness, which is not yet ported"] +#[ignore = "missing init_temp_composer and get_application_tester (ApplicationTester) test infrastructure"] fn test_set_and_get_url_in_repository_assoc() { todo!() } #[test] -#[ignore = "requires the ApplicationTester/initTempComposer harness, which is not yet ported"] +#[ignore = "missing init_temp_composer and get_application_tester (ApplicationTester) test infrastructure"] fn test_set_and_get_url_in_repository_list() { todo!() } #[test] -#[ignore = "requires the ApplicationTester/initTempComposer harness, which is not yet ported"] +#[ignore = "missing init_temp_composer and get_application_tester (ApplicationTester) test infrastructure"] fn test_disable_and_enable_packagist() { todo!() } #[test] -#[ignore = "requires the ApplicationTester/initTempComposer harness, which is not yet ported"] +#[ignore = "missing init_temp_composer and get_application_tester (ApplicationTester) test infrastructure"] fn test_invalid_arg_combination_throws() { todo!() } #[test] -#[ignore = "requires the ApplicationTester/initTempComposer harness, which is not yet ported"] +#[ignore = "missing init_temp_composer and get_application_tester (ApplicationTester) test infrastructure"] fn test_prepend_repository_by_name_list_to_assoc() { todo!() } #[test] -#[ignore = "requires the ApplicationTester/initTempComposer harness, which is not yet ported"] +#[ignore = "missing init_temp_composer and get_application_tester (ApplicationTester) test infrastructure"] fn test_append_repository_by_name_list_to_assoc() { todo!() } #[test] -#[ignore = "requires the ApplicationTester/initTempComposer harness, which is not yet ported"] +#[ignore = "missing init_temp_composer and get_application_tester (ApplicationTester) test infrastructure"] fn test_prepend_repository_assoc_with_packagist_disabled() { todo!() } #[test] -#[ignore = "requires the ApplicationTester/initTempComposer harness, which is not yet ported"] +#[ignore = "missing init_temp_composer and get_application_tester (ApplicationTester) test infrastructure"] fn test_append_repository_assoc_with_packagist_disabled() { todo!() } #[test] -#[ignore = "requires the ApplicationTester/initTempComposer harness, which is not yet ported"] +#[ignore = "missing init_temp_composer and get_application_tester (ApplicationTester) test infrastructure"] fn test_add_before_and_after_by_name() { todo!() } #[test] -#[ignore = "requires the ApplicationTester/initTempComposer harness, which is not yet ported"] +#[ignore = "missing init_temp_composer and get_application_tester (ApplicationTester) test infrastructure"] fn test_add_same_name_replaces_existing() { todo!() } diff --git a/crates/shirabe/tests/command/require_command_test.rs b/crates/shirabe/tests/command/require_command_test.rs index 470bbe2..0f43342 100644 --- a/crates/shirabe/tests/command/require_command_test.rs +++ b/crates/shirabe/tests/command/require_command_test.rs @@ -1,25 +1,25 @@ //! ref: composer/tests/Composer/Test/Command/RequireCommandTest.php #[test] -#[ignore = "requires the ApplicationTester/initTempComposer harness, which is not yet ported"] +#[ignore = "requires ApplicationTester and TestCase::initTempComposer/getApplicationTester harness (not implemented)"] fn test_require_throws_if_none_matches() { todo!() } #[test] -#[ignore = "requires the ApplicationTester/initTempComposer harness, which is not yet ported"] +#[ignore = "requires ApplicationTester and TestCase::initTempComposer/getApplicationTester/setInputs harness (not implemented)"] fn test_require_warns_if_resolved_to_feature_branch() { todo!() } #[test] -#[ignore = "requires the ApplicationTester/initTempComposer harness, which is not yet ported"] +#[ignore = "requires ApplicationTester and TestCase::initTempComposer/getApplicationTester harness (not implemented)"] fn test_require() { todo!() } #[test] -#[ignore = "requires the ApplicationTester/initTempComposer harness, which is not yet ported"] +#[ignore = "requires ApplicationTester and TestCase::initTempComposer/createComposerLock/createInstalledJson/getApplicationTester harness (not implemented)"] fn test_inconsistent_require_keys() { todo!() } diff --git a/crates/shirabe/tests/command/run_script_command_test.rs b/crates/shirabe/tests/command/run_script_command_test.rs index 14766d4..366ee96 100644 --- a/crates/shirabe/tests/command/run_script_command_test.rs +++ b/crates/shirabe/tests/command/run_script_command_test.rs @@ -1,31 +1,31 @@ //! ref: composer/tests/Composer/Test/Command/RunScriptCommandTest.php #[test] -#[ignore = "requires the ApplicationTester/initTempComposer harness, which is not yet ported"] +#[ignore = "requires PHPUnit getMockBuilder/onlyMethods partial mock of RunScriptCommand (override requireComposer/initialize/etc) plus expects()/with()/willReturn()/returnValueMap mocks of InputInterface/OutputInterface/EventDispatcher with a callback constraint on ScriptEvent; no mocking infrastructure exists"] fn test_detect_and_pass_dev_mode_to_event_and_to_dispatching() { todo!() } #[test] -#[ignore = "requires the ApplicationTester/initTempComposer harness, which is not yet ported"] +#[ignore = "requires init_temp_composer and get_application_tester (ApplicationTester) infrastructure; neither exists"] fn test_can_list_scripts() { todo!() } #[test] -#[ignore = "requires the ApplicationTester/initTempComposer harness, which is not yet ported"] +#[ignore = "requires init_temp_composer and get_application_tester (ApplicationTester) infrastructure; neither exists"] fn test_can_define_aliases() { todo!() } #[test] -#[ignore = "requires the ApplicationTester/initTempComposer harness, which is not yet ported"] +#[ignore = "requires init_temp_composer/get_application_tester (ApplicationTester) plus writing and executing a PHP-generated Symfony Command class (file_put_contents MyCommand.php); fundamentally unportable, no infrastructure exists"] fn test_execution_of_simple_symfony_command() { todo!() } #[test] -#[ignore = "requires the ApplicationTester/initTempComposer harness, which is not yet ported"] +#[ignore = "requires init_temp_composer/get_application_tester (ApplicationTester) plus writing and executing a PHP-generated Symfony Command class (file_put_contents MyCommandWithDefinitions.php); fundamentally unportable, no infrastructure exists"] fn test_execution_of_symfony_command_with_configuration() { todo!() } diff --git a/crates/shirabe/tests/command/search_command_test.rs b/crates/shirabe/tests/command/search_command_test.rs index d5b6a76..5cad1eb 100644 --- a/crates/shirabe/tests/command/search_command_test.rs +++ b/crates/shirabe/tests/command/search_command_test.rs @@ -1,19 +1,19 @@ //! ref: composer/tests/Composer/Test/Command/SearchCommandTest.php #[test] -#[ignore = "requires the ApplicationTester/initTempComposer harness, which is not yet ported"] +#[ignore = "missing TestCase::init_temp_composer and get_application_tester (ApplicationTester) infrastructure"] fn test_search() { todo!() } #[test] -#[ignore = "requires the ApplicationTester/initTempComposer harness, which is not yet ported"] +#[ignore = "missing TestCase::init_temp_composer and get_application_tester (ApplicationTester) infrastructure"] fn test_invalid_format() { todo!() } #[test] -#[ignore = "requires the ApplicationTester/initTempComposer harness, which is not yet ported"] +#[ignore = "missing TestCase::init_temp_composer and get_application_tester (ApplicationTester) infrastructure"] fn test_invalid_flags() { todo!() } diff --git a/crates/shirabe/tests/command/self_update_command_test.rs b/crates/shirabe/tests/command/self_update_command_test.rs index d741302..8e51580 100644 --- a/crates/shirabe/tests/command/self_update_command_test.rs +++ b/crates/shirabe/tests/command/self_update_command_test.rs @@ -7,7 +7,7 @@ fn set_up() -> String { } #[test] -#[ignore = "requires the ApplicationTester harness, which is not yet ported"] +#[ignore = "depends on initTempComposer, composer-test.phar fixture, and Symfony Process to spawn the phar; none ported"] fn test_successful_update() { let _phar = set_up(); @@ -15,7 +15,7 @@ fn test_successful_update() { } #[test] -#[ignore = "requires the ApplicationTester harness, which is not yet ported"] +#[ignore = "depends on initTempComposer, composer-test.phar fixture, and Symfony Process to spawn the phar; none ported"] fn test_update_to_specific_version() { let _phar = set_up(); @@ -23,7 +23,7 @@ fn test_update_to_specific_version() { } #[test] -#[ignore = "requires the ApplicationTester harness, which is not yet ported"] +#[ignore = "depends on getApplicationTester (ApplicationTester) which is not ported"] fn test_update_with_invalid_option_throws_exception() { let _phar = set_up(); @@ -31,7 +31,7 @@ fn test_update_with_invalid_option_throws_exception() { } #[test] -#[ignore = "requires the ApplicationTester harness, which is not yet ported"] +#[ignore = "depends on initTempComposer, composer-test.phar fixture, and Symfony Process to spawn the phar; none ported"] fn test_update_to_different_channel() { let _phar = set_up(); diff --git a/crates/shirabe/tests/command/show_command_test.rs b/crates/shirabe/tests/command/show_command_test.rs index b17ce22..56e8106 100644 --- a/crates/shirabe/tests/command/show_command_test.rs +++ b/crates/shirabe/tests/command/show_command_test.rs @@ -1,139 +1,139 @@ //! ref: composer/tests/Composer/Test/Command/ShowCommandTest.php +#[ignore = "missing TestCase::init_temp_composer, create_installed_json, create_composer_lock, and get_application_tester (ApplicationTester) infrastructure"] #[test] -#[ignore = "requires the ApplicationTester/initTempComposer harness, which is not yet ported"] fn test_show() { todo!() } +#[ignore = "missing TestCase::init_temp_composer, create_installed_json, and get_application_tester (ApplicationTester) infrastructure"] #[test] -#[ignore = "requires the ApplicationTester/initTempComposer harness, which is not yet ported"] fn test_outdated_filters_according_to_platform_reqs_and_warns() { todo!() } +#[ignore = "missing TestCase::init_temp_composer, create_installed_json, and get_application_tester (ApplicationTester) infrastructure"] #[test] -#[ignore = "requires the ApplicationTester/initTempComposer harness, which is not yet ported"] fn test_outdated_filters_according_to_platform_reqs_without_warning_for_higher_versions() { todo!() } +#[ignore = "missing TestCase::init_temp_composer, create_installed_json, configure_links, and get_application_tester (ApplicationTester) infrastructure"] #[test] -#[ignore = "requires the ApplicationTester/initTempComposer harness, which is not yet ported"] fn test_show_direct_with_name_does_not_show_transient_dependencies() { todo!() } +#[ignore = "missing TestCase::init_temp_composer, create_installed_json, and get_application_tester (ApplicationTester) infrastructure"] #[test] -#[ignore = "requires the ApplicationTester/initTempComposer harness, which is not yet ported"] fn test_show_direct_with_name_only_shows_direct_dependents() { todo!() } +#[ignore = "missing TestCase::init_temp_composer, create_installed_json, and get_application_tester (ApplicationTester) infrastructure"] #[test] -#[ignore = "requires the ApplicationTester/initTempComposer harness, which is not yet ported"] fn test_show_platform_only_shows_platform_packages() { todo!() } +#[ignore = "missing TestCase::init_temp_composer and get_application_tester (ApplicationTester) infrastructure"] #[test] -#[ignore = "requires the ApplicationTester/initTempComposer harness, which is not yet ported"] fn test_show_platform_works_without_composer_json() { todo!() } +#[ignore = "missing TestCase::init_temp_composer, create_installed_json, and get_application_tester (ApplicationTester) infrastructure"] #[test] -#[ignore = "requires the ApplicationTester/initTempComposer harness, which is not yet ported"] fn test_outdated_with_zero_major() { todo!() } +#[ignore = "missing TestCase::init_temp_composer, create_installed_json, create_composer_lock, and get_application_tester (ApplicationTester) infrastructure"] #[test] -#[ignore = "requires the ApplicationTester/initTempComposer harness, which is not yet ported"] fn test_show_all_shows_all_sections() { todo!() } +#[ignore = "missing TestCase::init_temp_composer and get_application_tester (ApplicationTester) infrastructure"] #[test] -#[ignore = "requires the ApplicationTester/initTempComposer harness, which is not yet ported"] fn test_locked_requires_valid_lock_file() { todo!() } +#[ignore = "missing TestCase::init_temp_composer, create_composer_lock, and get_application_tester (ApplicationTester) infrastructure"] #[test] -#[ignore = "requires the ApplicationTester/initTempComposer harness, which is not yet ported"] fn test_locked_shows_all_locked() { todo!() } +#[ignore = "missing TestCase::get_application_tester (ApplicationTester) infrastructure"] #[test] -#[ignore = "requires the ApplicationTester/initTempComposer harness, which is not yet ported"] fn test_invalid_option_combinations() { todo!() } +#[ignore = "missing TestCase::init_temp_composer and get_application_tester (ApplicationTester) infrastructure"] #[test] -#[ignore = "requires the ApplicationTester/initTempComposer harness, which is not yet ported"] fn test_ignored_option_combinations() { todo!() } +#[ignore = "missing TestCase::init_temp_composer and get_application_tester (ApplicationTester) infrastructure"] #[test] -#[ignore = "requires the ApplicationTester/initTempComposer harness, which is not yet ported"] fn test_self_and_name_only() { todo!() } +#[ignore = "missing TestCase::init_temp_composer and get_application_tester (ApplicationTester) infrastructure"] #[test] -#[ignore = "requires the ApplicationTester/initTempComposer harness, which is not yet ported"] fn test_self_and_package_combination() { todo!() } +#[ignore = "missing TestCase::init_temp_composer and get_application_tester (ApplicationTester) infrastructure"] #[test] -#[ignore = "requires the ApplicationTester/initTempComposer harness, which is not yet ported"] fn test_self() { todo!() } +#[ignore = "missing TestCase::init_temp_composer and get_application_tester (ApplicationTester) infrastructure"] #[test] -#[ignore = "requires the ApplicationTester/initTempComposer harness, which is not yet ported"] fn test_not_installed_error() { todo!() } +#[ignore = "missing TestCase::init_temp_composer, create_installed_json, and get_application_tester (ApplicationTester) infrastructure"] #[test] -#[ignore = "requires the ApplicationTester/initTempComposer harness, which is not yet ported"] fn test_no_dev_option() { todo!() } +#[ignore = "missing TestCase::init_temp_composer, create_installed_json, and get_application_tester (ApplicationTester) infrastructure"] #[test] -#[ignore = "requires the ApplicationTester/initTempComposer harness, which is not yet ported"] fn test_package_filter() { todo!() } +#[ignore = "missing TestCase::init_temp_composer, create_installed_json, create_composer_lock, and get_application_tester (ApplicationTester) infrastructure"] #[test] -#[ignore = "requires the ApplicationTester/initTempComposer harness, which is not yet ported"] fn test_not_existing_package() { todo!() } +#[ignore = "missing TestCase::init_temp_composer, create_installed_json, and get_application_tester (ApplicationTester) infrastructure"] #[test] -#[ignore = "requires the ApplicationTester/initTempComposer harness, which is not yet ported"] fn test_not_existing_package_with_working_dir() { todo!() } +#[ignore = "missing TestCase::init_temp_composer, create_installed_json, and get_application_tester (ApplicationTester) infrastructure"] #[test] -#[ignore = "requires the ApplicationTester/initTempComposer harness, which is not yet ported"] fn test_specific_package_and_tree() { todo!() } +#[ignore = "missing TestCase::init_temp_composer, create_installed_json, and get_application_tester (ApplicationTester) infrastructure"] #[test] -#[ignore = "requires the ApplicationTester/initTempComposer harness, which is not yet ported"] fn test_name_only_prints_no_trailing_whitespace() { todo!() } diff --git a/crates/shirabe/tests/command/status_command_test.rs b/crates/shirabe/tests/command/status_command_test.rs index 6b8dcf6..6154ea0 100644 --- a/crates/shirabe/tests/command/status_command_test.rs +++ b/crates/shirabe/tests/command/status_command_test.rs @@ -1,13 +1,13 @@ //! ref: composer/tests/Composer/Test/Command/StatusCommandTest.php +#[ignore = "missing TestCase::init_temp_composer, create_composer_lock, create_installed_json, and get_application_tester (ApplicationTester) infrastructure"] #[test] -#[ignore = "requires the ApplicationTester/initTempComposer harness, which is not yet ported"] fn test_no_local_changes() { todo!() } +#[ignore = "missing TestCase::init_temp_composer and get_application_tester (ApplicationTester) infrastructure"] #[test] -#[ignore = "requires the ApplicationTester/initTempComposer harness, which is not yet ported"] fn test_locally_modified_packages() { todo!() } diff --git a/crates/shirabe/tests/command/suggests_command_test.rs b/crates/shirabe/tests/command/suggests_command_test.rs index ca4d9a2..c23691b 100644 --- a/crates/shirabe/tests/command/suggests_command_test.rs +++ b/crates/shirabe/tests/command/suggests_command_test.rs @@ -1,13 +1,13 @@ //! ref: composer/tests/Composer/Test/Command/SuggestsCommandTest.php +#[ignore = "missing TestCase::init_temp_composer, create_installed_json, create_composer_lock, and get_application_tester (ApplicationTester) infrastructure"] #[test] -#[ignore = "requires the ApplicationTester/initTempComposer harness, which is not yet ported"] fn test_installed_packages_with_no_suggestions() { todo!() } +#[ignore = "missing TestCase::init_temp_composer, create_installed_json, create_composer_lock, get_application_tester (ApplicationTester), and get_version_parser infrastructure"] #[test] -#[ignore = "requires the ApplicationTester/initTempComposer harness, which is not yet ported"] fn test_suggest() { todo!() } diff --git a/crates/shirabe/tests/command/update_command_test.rs b/crates/shirabe/tests/command/update_command_test.rs index ceae0eb..95b6962 100644 --- a/crates/shirabe/tests/command/update_command_test.rs +++ b/crates/shirabe/tests/command/update_command_test.rs @@ -1,43 +1,43 @@ //! ref: composer/tests/Composer/Test/Command/UpdateCommandTest.php #[test] -#[ignore = "requires the ApplicationTester/initTempComposer harness, which is not yet ported"] +#[ignore = "requires ApplicationTester and TestCase::initTempComposer/createComposerLock harness (not implemented)"] fn test_update() { todo!() } #[test] -#[ignore = "requires the ApplicationTester/initTempComposer harness, which is not yet ported"] +#[ignore = "requires ApplicationTester and TestCase::initTempComposer/createComposerLock harness (not implemented)"] fn test_update_with_patch_only() { todo!() } #[test] -#[ignore = "requires the ApplicationTester/initTempComposer harness, which is not yet ported"] +#[ignore = "requires ApplicationTester and TestCase::initTempComposer/createComposerLock harness (not implemented)"] fn test_interactive_mode_throws_if_no_package_to_update() { todo!() } #[test] -#[ignore = "requires the ApplicationTester/initTempComposer harness, which is not yet ported"] +#[ignore = "requires ApplicationTester and TestCase::initTempComposer/createComposerLock harness (not implemented)"] fn test_interactive_mode_throws_if_no_package_entered() { todo!() } #[test] -#[ignore = "requires the ApplicationTester/initTempComposer harness, which is not yet ported"] +#[ignore = "requires ApplicationTester and TestCase::initTempComposer/createComposerLock/createInstalledJson harness (not implemented)"] fn test_interactive_tmp() { todo!() } #[test] -#[ignore = "requires the ApplicationTester/initTempComposer harness, which is not yet ported"] +#[ignore = "requires ApplicationTester and TestCase::initTempComposer harness (not implemented)"] fn test_no_security_blocking_allows_insecure_packages() { todo!() } #[test] -#[ignore = "requires the ApplicationTester/initTempComposer harness, which is not yet ported"] +#[ignore = "requires ApplicationTester and TestCase::initTempComposer harness (not implemented)"] fn test_bump_after_update_without_lockfile() { todo!() } diff --git a/crates/shirabe/tests/command/validate_command_test.rs b/crates/shirabe/tests/command/validate_command_test.rs index 26af2ae..b306100 100644 --- a/crates/shirabe/tests/command/validate_command_test.rs +++ b/crates/shirabe/tests/command/validate_command_test.rs @@ -1,25 +1,25 @@ //! ref: composer/tests/Composer/Test/Command/ValidateCommandTest.php #[test] -#[ignore = "requires the ApplicationTester/initTempComposer harness, which is not yet ported"] +#[ignore = "missing TestCase::init_temp_composer and get_application_tester (ApplicationTester with run/get_display) infrastructure"] fn test_validate() { todo!() } #[test] -#[ignore = "requires the ApplicationTester/initTempComposer harness, which is not yet ported"] +#[ignore = "missing TestCase::init_temp_composer and get_application_tester (ApplicationTester with run/get_display) infrastructure"] fn test_validate_on_file_issues() { todo!() } #[test] -#[ignore = "requires the ApplicationTester/initTempComposer harness, which is not yet ported"] +#[ignore = "missing TestCase::init_temp_composer / create_composer_lock and get_application_tester (ApplicationTester) infrastructure"] fn test_with_composer_lock() { todo!() } #[test] -#[ignore = "requires the ApplicationTester/initTempComposer harness, which is not yet ported"] +#[ignore = "missing TestCase::init_temp_composer and get_application_tester (ApplicationTester with run/get_display/get_status_code) infrastructure"] fn test_unaccessible_file() { todo!() } diff --git a/crates/shirabe/tests/completion_functional_test.rs b/crates/shirabe/tests/completion_functional_test.rs index 9dc0482..06f8dd8 100644 --- a/crates/shirabe/tests/completion_functional_test.rs +++ b/crates/shirabe/tests/completion_functional_test.rs @@ -1,7 +1,7 @@ //! ref: composer/tests/Composer/Test/CompletionFunctionalTest.php #[test] -#[ignore = "requires the console completion harness (Application + CompletionInput), which is not yet ported"] +#[ignore = "CommandCompletionTester (Symfony Console test helper) is not implemented in the port"] fn test_complete() { todo!() } diff --git a/crates/shirabe/tests/config/json_config_source_test.rs b/crates/shirabe/tests/config/json_config_source_test.rs index b8abf57..974c5da 100644 --- a/crates/shirabe/tests/config/json_config_source_test.rs +++ b/crates/shirabe/tests/config/json_config_source_test.rs @@ -1,7 +1,14 @@ //! ref: composer/tests/Composer/Test/Config/JsonConfigSourceTest.php +use indexmap::IndexMap; +use shirabe::config::ConfigSourceInterface; +use shirabe::config::JsonConfigSource; +use shirabe::json::JsonFile; use shirabe::util::filesystem::Filesystem; +use shirabe_php_shim::PhpMixed; +use std::cell::RefCell; use std::path::PathBuf; +use std::rc::Rc; use tempfile::TempDir; fn set_up() -> TearDown { @@ -31,58 +38,455 @@ impl Drop for TearDown { } } +fn fixture_path(name: &str) -> PathBuf { + PathBuf::from(format!( + "{}/../../composer/tests/Composer/Test/Config/Fixtures/{}", + env!("CARGO_MANIFEST_DIR"), + name + )) +} + +fn assert_file_equals(expected: &std::path::Path, actual: &std::path::Path) { + let expected_contents = std::fs::read(expected).unwrap(); + let actual_contents = std::fs::read(actual).unwrap(); + assert_eq!( + expected_contents, actual_contents, + "Failed asserting that file {:?} matches {:?}", + actual, expected + ); +} + +fn json_config_source(config: &std::path::Path) -> JsonConfigSource { + let json_file = JsonFile::new(config.to_string_lossy().to_string(), None, None).unwrap(); + JsonConfigSource::new(Rc::new(RefCell::new(json_file)), false) +} + +#[ignore] #[test] -#[ignore = "test body not yet ported (todo!() stub)"] fn test_add_repository() { - let _tear_down = set_up(); - todo!() + let tear_down = set_up(); + let config = tear_down.working_dir().join("composer.json"); + std::fs::copy(fixture_path("composer-repositories.json"), &config).unwrap(); + let mut json_config_source = json_config_source(&config); + + let mut repo = IndexMap::new(); + repo.insert("type".to_string(), PhpMixed::String("git".to_string())); + repo.insert( + "url".to_string(), + PhpMixed::String("example.tld".to_string()), + ); + json_config_source + .add_repository("example_tld", PhpMixed::Array(repo), true) + .unwrap(); + + assert_file_equals( + &fixture_path("config/config-with-exampletld-repository.json"), + &config, + ); } +#[ignore] #[test] -#[ignore = "test body not yet ported (todo!() stub)"] fn test_add_repository_as_list() { - let _tear_down = set_up(); - todo!() + let tear_down = set_up(); + let config = tear_down.working_dir().join("composer.json"); + std::fs::copy(fixture_path("composer-repositories.json"), &config).unwrap(); + let mut json_config_source = json_config_source(&config); + + let mut repo = IndexMap::new(); + repo.insert("type".to_string(), PhpMixed::String("git".to_string())); + repo.insert( + "url".to_string(), + PhpMixed::String("example.tld".to_string()), + ); + json_config_source + .add_repository("", PhpMixed::Array(repo), true) + .unwrap(); + + assert_file_equals( + &fixture_path("config/config-with-exampletld-repository-as-list.json"), + &config, + ); } +#[ignore] #[test] -#[ignore = "test body not yet ported (todo!() stub)"] fn test_add_repository_with_options() { - let _tear_down = set_up(); - todo!() + let tear_down = set_up(); + let config = tear_down.working_dir().join("composer.json"); + std::fs::copy(fixture_path("composer-repositories.json"), &config).unwrap(); + let mut json_config_source = json_config_source(&config); + + let mut repo = IndexMap::new(); + repo.insert("type".to_string(), PhpMixed::String("composer".to_string())); + repo.insert( + "url".to_string(), + PhpMixed::String("https://example.tld".to_string()), + ); + { + let mut local_cert = IndexMap::new(); + local_cert.insert( + "local_cert".to_string(), + PhpMixed::String("/home/composer/.ssl/composer.pem".to_string()), + ); + let mut ssl = IndexMap::new(); + ssl.insert("ssl".to_string(), PhpMixed::Array(local_cert)); + repo.insert("options".to_string(), PhpMixed::Array(ssl)); + } + + json_config_source + .add_repository("example_tld", PhpMixed::Array(repo), true) + .unwrap(); + + assert_file_equals( + &fixture_path("config/config-with-exampletld-repository-and-options.json"), + &config, + ); } +#[ignore] #[test] -#[ignore = "test body not yet ported (todo!() stub)"] fn test_remove_repository() { - let _tear_down = set_up(); - todo!() + let tear_down = set_up(); + let config = tear_down.working_dir().join("composer.json"); + std::fs::copy( + fixture_path("config/config-with-exampletld-repository.json"), + &config, + ) + .unwrap(); + let mut json_config_source = json_config_source(&config); + json_config_source.remove_repository("example_tld").unwrap(); + + assert_file_equals(&fixture_path("composer-empty.json"), &config); } +#[ignore] #[test] -#[ignore = "test body not yet ported (todo!() stub)"] fn test_add_packagist_repository_with_false_value() { - let _tear_down = set_up(); - todo!() + let tear_down = set_up(); + let config = tear_down.working_dir().join("composer.json"); + std::fs::copy(fixture_path("composer-repositories.json"), &config).unwrap(); + let mut json_config_source = json_config_source(&config); + json_config_source + .add_repository("packagist", PhpMixed::Bool(false), true) + .unwrap(); + + assert_file_equals( + &fixture_path("config/config-with-packagist-false.json"), + &config, + ); } +#[ignore] #[test] -#[ignore = "test body not yet ported (todo!() stub)"] fn test_remove_packagist() { - let _tear_down = set_up(); - todo!() + let tear_down = set_up(); + let config = tear_down.working_dir().join("composer.json"); + std::fs::copy( + fixture_path("config/config-with-packagist-false.json"), + &config, + ) + .unwrap(); + let mut json_config_source = json_config_source(&config); + json_config_source.remove_repository("packagist").unwrap(); + + assert_file_equals(&fixture_path("composer-empty.json"), &config); +} + +/// Mirror of provideAddLinkData(): (sourceFile, type, name, value, compareAgainst). +fn provide_add_link_data() -> Vec<(PathBuf, &'static str, &'static str, &'static str, PathBuf)> { + let empty = fixture_path("composer-empty.json"); + let one_of_everything = fixture_path("composer-one-of-everything.json"); + let two_of_everything = fixture_path("composer-two-of-everything.json"); + + let add_link_data_arguments = |r#type: &'static str, + name: &'static str, + value: &'static str, + fixture_basename: &str, + before: &PathBuf| { + ( + before.clone(), + r#type, + name, + value, + fixture_path(&format!("addLink/{}.json", fixture_basename)), + ) + }; + + vec![ + add_link_data_arguments( + "require", + "my-vend/my-lib", + "1.*", + "require-from-empty", + &empty, + ), + add_link_data_arguments( + "require", + "my-vend/my-lib", + "1.*", + "require-from-oneOfEverything", + &one_of_everything, + ), + add_link_data_arguments( + "require", + "my-vend/my-lib", + "1.*", + "require-from-twoOfEverything", + &two_of_everything, + ), + add_link_data_arguments( + "require-dev", + "my-vend/my-lib-tests", + "1.*", + "require-dev-from-empty", + &empty, + ), + add_link_data_arguments( + "require-dev", + "my-vend/my-lib-tests", + "1.*", + "require-dev-from-oneOfEverything", + &one_of_everything, + ), + add_link_data_arguments( + "require-dev", + "my-vend/my-lib-tests", + "1.*", + "require-dev-from-twoOfEverything", + &two_of_everything, + ), + add_link_data_arguments( + "provide", + "my-vend/my-lib-interface", + "1.*", + "provide-from-empty", + &empty, + ), + add_link_data_arguments( + "provide", + "my-vend/my-lib-interface", + "1.*", + "provide-from-oneOfEverything", + &one_of_everything, + ), + add_link_data_arguments( + "provide", + "my-vend/my-lib-interface", + "1.*", + "provide-from-twoOfEverything", + &two_of_everything, + ), + add_link_data_arguments( + "suggest", + "my-vend/my-optional-extension", + "1.*", + "suggest-from-empty", + &empty, + ), + add_link_data_arguments( + "suggest", + "my-vend/my-optional-extension", + "1.*", + "suggest-from-oneOfEverything", + &one_of_everything, + ), + add_link_data_arguments( + "suggest", + "my-vend/my-optional-extension", + "1.*", + "suggest-from-twoOfEverything", + &two_of_everything, + ), + add_link_data_arguments( + "replace", + "my-vend/other-app", + "1.*", + "replace-from-empty", + &empty, + ), + add_link_data_arguments( + "replace", + "my-vend/other-app", + "1.*", + "replace-from-oneOfEverything", + &one_of_everything, + ), + add_link_data_arguments( + "replace", + "my-vend/other-app", + "1.*", + "replace-from-twoOfEverything", + &two_of_everything, + ), + add_link_data_arguments( + "conflict", + "my-vend/my-old-app", + "1.*", + "conflict-from-empty", + &empty, + ), + add_link_data_arguments( + "conflict", + "my-vend/my-old-app", + "1.*", + "conflict-from-oneOfEverything", + &one_of_everything, + ), + add_link_data_arguments( + "conflict", + "my-vend/my-old-app", + "1.*", + "conflict-from-twoOfEverything", + &two_of_everything, + ), + ] } +#[ignore] #[test] -#[ignore = "test body not yet ported (todo!() stub)"] fn test_add_link() { - let _tear_down = set_up(); - todo!() + for (source_file, r#type, name, value, compare_against) in provide_add_link_data() { + let tear_down = set_up(); + let composer_json = tear_down.working_dir().join("composer.json"); + std::fs::copy(&source_file, &composer_json).unwrap(); + let mut json_config_source = json_config_source(&composer_json); + + json_config_source.add_link(r#type, name, value).unwrap(); + + assert_file_equals(&compare_against, &composer_json); + } } +/// Mirror of provideRemoveLinkData(): (sourceFile, type, name, compareAgainst). +fn provide_remove_link_data() -> Vec<(PathBuf, &'static str, &'static str, PathBuf)> { + let one_of_everything = fixture_path("composer-one-of-everything.json"); + let two_of_everything = fixture_path("composer-two-of-everything.json"); + + let remove_link_data_arguments = + |r#type: &'static str, + name: &'static str, + fixture_basename: &str, + after: Option<&PathBuf>| { + let after = after.cloned().unwrap_or_else(|| { + fixture_path(&format!("removeLink/{}-after.json", fixture_basename)) + }); + ( + fixture_path(&format!("removeLink/{}.json", fixture_basename)), + r#type, + name, + after, + ) + }; + + vec![ + remove_link_data_arguments("require", "my-vend/my-lib", "require-to-empty", None), + remove_link_data_arguments( + "require", + "my-vend/my-lib", + "require-to-oneOfEverything", + Some(&one_of_everything), + ), + remove_link_data_arguments( + "require", + "my-vend/my-lib", + "require-to-twoOfEverything", + Some(&two_of_everything), + ), + remove_link_data_arguments( + "require-dev", + "my-vend/my-lib-tests", + "require-dev-to-empty", + None, + ), + remove_link_data_arguments( + "require-dev", + "my-vend/my-lib-tests", + "require-dev-to-oneOfEverything", + Some(&one_of_everything), + ), + remove_link_data_arguments( + "require-dev", + "my-vend/my-lib-tests", + "require-dev-to-twoOfEverything", + Some(&two_of_everything), + ), + remove_link_data_arguments( + "provide", + "my-vend/my-lib-interface", + "provide-to-empty", + None, + ), + remove_link_data_arguments( + "provide", + "my-vend/my-lib-interface", + "provide-to-oneOfEverything", + Some(&one_of_everything), + ), + remove_link_data_arguments( + "provide", + "my-vend/my-lib-interface", + "provide-to-twoOfEverything", + Some(&two_of_everything), + ), + remove_link_data_arguments( + "suggest", + "my-vend/my-optional-extension", + "suggest-to-empty", + None, + ), + remove_link_data_arguments( + "suggest", + "my-vend/my-optional-extension", + "suggest-to-oneOfEverything", + Some(&one_of_everything), + ), + remove_link_data_arguments( + "suggest", + "my-vend/my-optional-extension", + "suggest-to-twoOfEverything", + Some(&two_of_everything), + ), + remove_link_data_arguments("replace", "my-vend/other-app", "replace-to-empty", None), + remove_link_data_arguments( + "replace", + "my-vend/other-app", + "replace-to-oneOfEverything", + Some(&one_of_everything), + ), + remove_link_data_arguments( + "replace", + "my-vend/other-app", + "replace-to-twoOfEverything", + Some(&two_of_everything), + ), + remove_link_data_arguments("conflict", "my-vend/my-old-app", "conflict-to-empty", None), + remove_link_data_arguments( + "conflict", + "my-vend/my-old-app", + "conflict-to-oneOfEverything", + Some(&one_of_everything), + ), + remove_link_data_arguments( + "conflict", + "my-vend/my-old-app", + "conflict-to-twoOfEverything", + Some(&two_of_everything), + ), + ] +} + +#[ignore] #[test] -#[ignore = "test body not yet ported (todo!() stub)"] fn test_remove_link() { - let _tear_down = set_up(); - todo!() + for (source_file, r#type, name, compare_against) in provide_remove_link_data() { + let tear_down = set_up(); + let composer_json = tear_down.working_dir().join("composer.json"); + std::fs::copy(&source_file, &composer_json).unwrap(); + let mut json_config_source = json_config_source(&composer_json); + + json_config_source.remove_link(r#type, name).unwrap(); + + assert_file_equals(&compare_against, &composer_json); + } } diff --git a/crates/shirabe/tests/config_test.rs b/crates/shirabe/tests/config_test.rs index b91237e..956e76b 100644 --- a/crates/shirabe/tests/config_test.rs +++ b/crates/shirabe/tests/config_test.rs @@ -1,9 +1,26 @@ //! ref: composer/tests/Composer/Test/ConfigTest.php use indexmap::IndexMap; +use shirabe::advisory::Auditor; use shirabe::config::Config; +use shirabe::util::Platform; use shirabe_php_shim::PhpMixed; +/// Builds a `['config' => {...}]` map for `Config::merge`. +fn config_section(pairs: Vec<(&str, PhpMixed)>) -> IndexMap<String, PhpMixed> { + let mut m: IndexMap<String, PhpMixed> = IndexMap::new(); + m.insert("config".to_string(), PhpMixed::Array(map(pairs))); + m +} + +/// PHP assertEquals on associative arrays compares pairs irrespective of order. +fn assert_map_equals(expected: &IndexMap<String, PhpMixed>, actual: &IndexMap<String, PhpMixed>) { + assert_eq!(expected.len(), actual.len()); + for (key, value) in expected { + assert_eq!(Some(value), actual.get(key), "key {key:?}"); + } +} + fn repo(r#type: &str, url: &str) -> PhpMixed { let mut m: IndexMap<String, PhpMixed> = IndexMap::new(); m.insert("type".to_string(), PhpMixed::String(r#type.to_string())); @@ -166,134 +183,588 @@ fn test_add_packagist_repository() { // htaccess-protect, var/realpath replacement, oauth, audit, ...) without the env isolation // their setUp/tearDown provides, or exercise plugin-config merge details. They are not // ported yet. +#[ignore] #[test] -#[ignore = "not yet ported (env-dependent without the setUp/tearDown isolation, or plugin-config merge details)"] fn test_preferred_install_as_string() { - todo!() + let mut config = Config::new(false, None); + config.merge( + &config_section(vec![( + "preferred-install", + PhpMixed::String("source".to_string()), + )]), + "test", + ); + config.merge( + &config_section(vec![( + "preferred-install", + PhpMixed::String("dist".to_string()), + )]), + "test", + ); + + assert_eq!( + PhpMixed::String("dist".to_string()), + config.get("preferred-install") + ); } +#[ignore] #[test] -#[ignore = "not yet ported (env-dependent without the setUp/tearDown isolation, or plugin-config merge details)"] fn test_merge_preferred_install() { - todo!() + let mut config = Config::new(false, None); + config.merge( + &config_section(vec![( + "preferred-install", + PhpMixed::String("dist".to_string()), + )]), + "test", + ); + config.merge( + &config_section(vec![( + "preferred-install", + PhpMixed::Array(map(vec![("foo/*", PhpMixed::String("source".to_string()))])), + )]), + "test", + ); + + // This assertion needs to make sure full wildcard preferences are placed last + // Handled by composer because we convert string preferences for BC, all other + // care for ordering and collision prevention is up to the user + let expected = map(vec![ + ("foo/*", PhpMixed::String("source".to_string())), + ("*", PhpMixed::String("dist".to_string())), + ]); + match config.get("preferred-install") { + PhpMixed::Array(actual) => assert_map_equals(&expected, &actual), + other => panic!("expected array, got {other:?}"), + } } +#[ignore] #[test] -#[ignore = "not yet ported (env-dependent without the setUp/tearDown isolation, or plugin-config merge details)"] fn test_merge_github_oauth() { - todo!() + let mut config = Config::new(false, None); + config.merge( + &config_section(vec![( + "github-oauth", + PhpMixed::Array(map(vec![("foo", PhpMixed::String("bar".to_string()))])), + )]), + "test", + ); + config.merge( + &config_section(vec![( + "github-oauth", + PhpMixed::Array(map(vec![("bar", PhpMixed::String("baz".to_string()))])), + )]), + "test", + ); + + let expected = map(vec![ + ("foo", PhpMixed::String("bar".to_string())), + ("bar", PhpMixed::String("baz".to_string())), + ]); + match config.get("github-oauth") { + PhpMixed::Array(actual) => assert_map_equals(&expected, &actual), + other => panic!("expected array, got {other:?}"), + } } +#[ignore] #[test] -#[ignore = "not yet ported (env-dependent without the setUp/tearDown isolation, or plugin-config merge details)"] fn test_var_replacement() { - todo!() + let mut config = Config::new(false, None); + config.merge( + &config_section(vec![ + ("a", PhpMixed::String("b".to_string())), + ("c", PhpMixed::String("{$a}".to_string())), + ]), + "test", + ); + config.merge( + &config_section(vec![ + ("bin-dir", PhpMixed::String("$HOME".to_string())), + ("cache-dir", PhpMixed::String("~/foo/".to_string())), + ]), + "test", + ); + + let home_raw = Platform::get_env("HOME") + .filter(|s| !s.is_empty()) + .or_else(|| Platform::get_env("USERPROFILE")) + .unwrap_or_default(); + let home = home_raw.trim_end_matches(['\\', '/']).to_string(); + assert_eq!(PhpMixed::String("b".to_string()), config.get("c")); + assert_eq!(PhpMixed::String(home.clone()), config.get("bin-dir")); + assert_eq!( + PhpMixed::String(format!("{}/foo", home)), + config.get("cache-dir") + ); } +#[ignore] #[test] -#[ignore = "not yet ported (env-dependent without the setUp/tearDown isolation, or plugin-config merge details)"] fn test_realpath_replacement() { - todo!() + let mut config = Config::new(false, Some("/foo/bar".to_string())); + config.merge( + &config_section(vec![ + ("bin-dir", PhpMixed::String("$HOME/foo".to_string())), + ("cache-dir", PhpMixed::String("/baz/".to_string())), + ("vendor-dir", PhpMixed::String("vendor".to_string())), + ]), + "test", + ); + + let home_raw = Platform::get_env("HOME") + .filter(|s| !s.is_empty()) + .or_else(|| Platform::get_env("USERPROFILE")) + .unwrap_or_default(); + let home = home_raw.trim_end_matches(['\\', '/']).to_string(); + assert_eq!( + PhpMixed::String("/foo/bar/vendor".to_string()), + config.get("vendor-dir") + ); + assert_eq!( + PhpMixed::String(format!("{}/foo", home)), + config.get("bin-dir") + ); + assert_eq!( + PhpMixed::String("/baz".to_string()), + config.get("cache-dir") + ); } +#[ignore] #[test] -#[ignore = "not yet ported (env-dependent without the setUp/tearDown isolation, or plugin-config merge details)"] fn test_stream_wrapper_dirs() { - todo!() + let mut config = Config::new(false, Some("/foo/bar".to_string())); + config.merge( + &config_section(vec![( + "cache-dir", + PhpMixed::String("s3://baz/".to_string()), + )]), + "test", + ); + + assert_eq!( + PhpMixed::String("s3://baz".to_string()), + config.get("cache-dir") + ); } +#[ignore] #[test] -#[ignore = "not yet ported (env-dependent without the setUp/tearDown isolation, or plugin-config merge details)"] fn test_fetching_relative_paths() { - todo!() + let mut config = Config::new(false, Some("/foo/bar".to_string())); + config.merge( + &config_section(vec![ + ("bin-dir", PhpMixed::String("{$vendor-dir}/foo".to_string())), + ("vendor-dir", PhpMixed::String("vendor".to_string())), + ]), + "test", + ); + + assert_eq!( + PhpMixed::String("/foo/bar/vendor".to_string()), + config.get("vendor-dir") + ); + assert_eq!( + PhpMixed::String("/foo/bar/vendor/foo".to_string()), + config.get("bin-dir") + ); + assert_eq!( + PhpMixed::String("vendor".to_string()), + config + .get_with_flags("vendor-dir", Config::RELATIVE_PATHS) + .unwrap() + ); + assert_eq!( + PhpMixed::String("vendor/foo".to_string()), + config + .get_with_flags("bin-dir", Config::RELATIVE_PATHS) + .unwrap() + ); } +#[ignore] #[test] -#[ignore = "not yet ported (env-dependent without the setUp/tearDown isolation, or plugin-config merge details)"] fn test_override_github_protocols() { - todo!() + let mut config = Config::new(false, None); + config.merge( + &config_section(vec![( + "github-protocols", + PhpMixed::List(vec![ + PhpMixed::String("https".to_string()), + PhpMixed::String("ssh".to_string()), + ]), + )]), + "test", + ); + config.merge( + &config_section(vec![( + "github-protocols", + PhpMixed::List(vec![PhpMixed::String("https".to_string())]), + )]), + "test", + ); + + assert_eq!( + PhpMixed::List(vec![PhpMixed::String("https".to_string())]), + config.get("github-protocols") + ); } +#[ignore] #[test] -#[ignore = "not yet ported (env-dependent without the setUp/tearDown isolation, or plugin-config merge details)"] fn test_git_disabled_by_default_in_github_protocols() { - todo!() + let mut config = Config::new(false, None); + config.merge( + &config_section(vec![( + "github-protocols", + PhpMixed::List(vec![ + PhpMixed::String("https".to_string()), + PhpMixed::String("git".to_string()), + ]), + )]), + "test", + ); + assert_eq!( + PhpMixed::List(vec![PhpMixed::String("https".to_string())]), + config.get("github-protocols") + ); + + config.merge( + &config_section(vec![("secure-http", PhpMixed::Bool(false))]), + "test", + ); + assert_eq!( + PhpMixed::List(vec![ + PhpMixed::String("https".to_string()), + PhpMixed::String("git".to_string()), + ]), + config.get("github-protocols") + ); } +#[ignore] #[test] -#[ignore = "not yet ported (env-dependent without the setUp/tearDown isolation, or plugin-config merge details)"] fn test_allowed_urls_pass() { - todo!() + let urls = vec![ + "https://packagist.org", + "git@github.com:composer/composer.git", + "hg://user:pass@my.satis/satis", + "\\\\myserver\\myplace.git", + "file://myserver.localhost/mygit.git", + "file://example.org/mygit.git", + "git:Department/Repo.git", + "ssh://[user@]host.xz[:port]/path/to/repo.git/", + ]; + for url in urls { + let mut config = Config::new(false, None); + config + .prohibit_url_by_config(url, None, &IndexMap::new()) + .unwrap(); + } } +#[ignore] #[test] -#[ignore = "not yet ported (env-dependent without the setUp/tearDown isolation, or plugin-config merge details)"] fn test_prohibited_urls_throw_exception() { - todo!() + let urls = vec![ + "http://packagist.org", + "http://10.1.0.1/satis", + "http://127.0.0.1/satis", + "http://\u{1F49B}@example.org", + "svn://localhost/trunk", + "svn://will.not.resolve/trunk", + "svn://192.168.0.1/trunk", + "svn://1.2.3.4/trunk", + "git://5.6.7.8/git.git", + ]; + for url in urls { + let mut config = Config::new(false, None); + let err = config + .prohibit_url_by_config(url, None, &IndexMap::new()) + .unwrap_err(); + assert!( + err.to_string().contains(&format!( + "Your configuration does not allow connections to {url}" + )), + "url {url:?}: {err}" + ); + } } +#[ignore = "requires getIOMock with expects() output expectations; no IO mocking infrastructure exists"] #[test] -#[ignore = "not yet ported (env-dependent without the setUp/tearDown isolation, or plugin-config merge details)"] fn test_prohibited_urls_warning_verify_peer() { todo!() } +#[ignore] #[test] -#[ignore = "not yet ported (env-dependent without the setUp/tearDown isolation, or plugin-config merge details)"] fn test_disable_tls_can_be_overridden() { - todo!() + let mut config = Config::new(true, None); + config.merge( + &config_section(vec![("disable-tls", PhpMixed::String("false".to_string()))]), + "test", + ); + assert_eq!(PhpMixed::Bool(false), config.get("disable-tls")); + config.merge( + &config_section(vec![("disable-tls", PhpMixed::String("true".to_string()))]), + "test", + ); + assert_eq!(PhpMixed::Bool(true), config.get("disable-tls")); } +#[ignore] #[test] -#[ignore = "not yet ported (env-dependent without the setUp/tearDown isolation, or plugin-config merge details)"] fn test_process_timeout() { - todo!() + Platform::put_env("COMPOSER_PROCESS_TIMEOUT", "0"); + let config = Config::new(true, None); + let result = config.get("process-timeout"); + Platform::clear_env("COMPOSER_PROCESS_TIMEOUT"); + + assert_eq!(PhpMixed::Int(0), result); } +#[ignore] #[test] -#[ignore = "not yet ported (env-dependent without the setUp/tearDown isolation, or plugin-config merge details)"] fn test_htaccess_protect() { - todo!() + Platform::put_env("COMPOSER_HTACCESS_PROTECT", "0"); + let config = Config::new(true, None); + let result = config.get("htaccess-protect"); + Platform::clear_env("COMPOSER_HTACCESS_PROTECT"); + + assert_eq!(PhpMixed::Bool(false), result); } +#[ignore] #[test] -#[ignore = "not yet ported (env-dependent without the setUp/tearDown isolation, or plugin-config merge details)"] fn test_get_source_of_value() { - todo!() + Platform::clear_env("COMPOSER_PROCESS_TIMEOUT"); + + let mut config = Config::new(true, None); + + assert_eq!( + Config::SOURCE_DEFAULT, + config.get_source_of_value("process-timeout").as_str() + ); + + config.merge( + &config_section(vec![("process-timeout", PhpMixed::Int(1))]), + "phpunit-test", + ); + + assert_eq!( + "phpunit-test", + config.get_source_of_value("process-timeout").as_str() + ); } +#[ignore] #[test] -#[ignore = "not yet ported (env-dependent without the setUp/tearDown isolation, or plugin-config merge details)"] fn test_get_source_of_value_env_variables() { - todo!() + Platform::put_env("COMPOSER_HTACCESS_PROTECT", "0"); + let mut config = Config::new(true, None); + let result = config.get_source_of_value("htaccess-protect"); + Platform::clear_env("COMPOSER_HTACCESS_PROTECT"); + + assert_eq!("COMPOSER_HTACCESS_PROTECT", result.as_str()); } +#[ignore] #[test] -#[ignore = "not yet ported (env-dependent without the setUp/tearDown isolation, or plugin-config merge details)"] fn test_audit() { - todo!() + let mut config = Config::new(true, None); + let result = config.get("audit"); + let result = result.as_array().unwrap(); + assert!(result.contains_key("abandoned")); + assert!(result.contains_key("ignore")); + assert_eq!( + Some(&PhpMixed::String(Auditor::ABANDONED_FAIL.to_string())), + result.get("abandoned") + ); + assert_eq!( + Some(&PhpMixed::Array(IndexMap::new())), + result.get("ignore") + ); + + Platform::put_env("COMPOSER_AUDIT_ABANDONED", Auditor::ABANDONED_IGNORE); + let result = config.get("audit"); + Platform::clear_env("COMPOSER_AUDIT_ABANDONED"); + let result = result.as_array().unwrap(); + assert!(result.contains_key("abandoned")); + assert!(result.contains_key("ignore")); + assert_eq!( + Some(&PhpMixed::String(Auditor::ABANDONED_IGNORE.to_string())), + result.get("abandoned") + ); + assert_eq!( + Some(&PhpMixed::Array(IndexMap::new())), + result.get("ignore") + ); + + config.merge( + &config_section(vec![( + "audit", + PhpMixed::Array(map(vec![( + "ignore", + PhpMixed::List(vec![ + PhpMixed::String("A".to_string()), + PhpMixed::String("B".to_string()), + ]), + )])), + )]), + "test", + ); + config.merge( + &config_section(vec![( + "audit", + PhpMixed::Array(map(vec![( + "ignore", + PhpMixed::List(vec![ + PhpMixed::String("A".to_string()), + PhpMixed::String("C".to_string()), + ]), + )])), + )]), + "test", + ); + let result = config.get("audit"); + let result = result.as_array().unwrap(); + assert!(result.contains_key("ignore")); + assert_eq!( + Some(&PhpMixed::List(vec![ + PhpMixed::String("A".to_string()), + PhpMixed::String("B".to_string()), + PhpMixed::String("A".to_string()), + PhpMixed::String("C".to_string()), + ])), + result.get("ignore") + ); + + // Test COMPOSER_SECURITY_BLOCKING_ABANDONED env var + Platform::put_env("COMPOSER_SECURITY_BLOCKING_ABANDONED", "1"); + let result = config.get("audit"); + Platform::clear_env("COMPOSER_SECURITY_BLOCKING_ABANDONED"); + let result = result.as_array().unwrap(); + assert!(result.contains_key("block-abandoned")); + assert_eq!(Some(&PhpMixed::Bool(true)), result.get("block-abandoned")); + + Platform::put_env("COMPOSER_SECURITY_BLOCKING_ABANDONED", "0"); + let result = config.get("audit"); + Platform::clear_env("COMPOSER_SECURITY_BLOCKING_ABANDONED"); + let result = result.as_array().unwrap(); + assert!(result.contains_key("block-abandoned")); + assert_eq!(Some(&PhpMixed::Bool(false)), result.get("block-abandoned")); } +#[ignore] #[test] -#[ignore = "not yet ported (env-dependent without the setUp/tearDown isolation, or plugin-config merge details)"] fn test_get_defaults_to_an_empty_array() { - todo!() + let config = Config::new(true, None); + let keys = [ + "bitbucket-oauth", + "github-oauth", + "gitlab-oauth", + "gitlab-token", + "forgejo-token", + "http-basic", + "bearer", + ]; + for key in keys { + let value = config.get(key); + match value { + PhpMixed::Array(m) => assert_eq!(0, m.len(), "key {key:?}"), + other => panic!("key {key:?}: expected array, got {other:?}"), + } + } } +#[ignore] #[test] -#[ignore = "not yet ported (env-dependent without the setUp/tearDown isolation, or plugin-config merge details)"] fn test_merges_plugin_config() { - todo!() + let mut config = Config::new(false, None); + config.merge( + &config_section(vec![( + "allow-plugins", + PhpMixed::Array(map(vec![("some/plugin", PhpMixed::Bool(true))])), + )]), + "test", + ); + match config.get("allow-plugins") { + PhpMixed::Array(actual) => { + assert_map_equals(&map(vec![("some/plugin", PhpMixed::Bool(true))]), &actual) + } + other => panic!("expected array, got {other:?}"), + } + + config.merge( + &config_section(vec![( + "allow-plugins", + PhpMixed::Array(map(vec![("another/plugin", PhpMixed::Bool(true))])), + )]), + "test", + ); + match config.get("allow-plugins") { + PhpMixed::Array(actual) => assert_map_equals( + &map(vec![ + ("some/plugin", PhpMixed::Bool(true)), + ("another/plugin", PhpMixed::Bool(true)), + ]), + &actual, + ), + other => panic!("expected array, got {other:?}"), + } } +#[ignore] #[test] -#[ignore = "not yet ported (env-dependent without the setUp/tearDown isolation, or plugin-config merge details)"] fn test_overrides_global_boolean_plugins_config() { - todo!() + let mut config = Config::new(false, None); + config.merge( + &config_section(vec![("allow-plugins", PhpMixed::Bool(true))]), + "test", + ); + assert_eq!(PhpMixed::Bool(true), config.get("allow-plugins")); + + config.merge( + &config_section(vec![( + "allow-plugins", + PhpMixed::Array(map(vec![("another/plugin", PhpMixed::Bool(true))])), + )]), + "test", + ); + match config.get("allow-plugins") { + PhpMixed::Array(actual) => assert_map_equals( + &map(vec![("another/plugin", PhpMixed::Bool(true))]), + &actual, + ), + other => panic!("expected array, got {other:?}"), + } } +#[ignore] #[test] -#[ignore = "not yet ported (env-dependent without the setUp/tearDown isolation, or plugin-config merge details)"] fn test_allows_all_plugins_from_local_boolean() { - todo!() + let mut config = Config::new(false, None); + config.merge( + &config_section(vec![( + "allow-plugins", + PhpMixed::Array(map(vec![("some/plugin", PhpMixed::Bool(true))])), + )]), + "test", + ); + match config.get("allow-plugins") { + PhpMixed::Array(actual) => { + assert_map_equals(&map(vec![("some/plugin", PhpMixed::Bool(true))]), &actual) + } + other => panic!("expected array, got {other:?}"), + } + + config.merge( + &config_section(vec![("allow-plugins", PhpMixed::Bool(true))]), + "test", + ); + assert_eq!(PhpMixed::Bool(true), config.get("allow-plugins")); } diff --git a/crates/shirabe/tests/dependency_resolver/default_policy_test.rs b/crates/shirabe/tests/dependency_resolver/default_policy_test.rs index 077dd71..776de6c 100644 --- a/crates/shirabe/tests/dependency_resolver/default_policy_test.rs +++ b/crates/shirabe/tests/dependency_resolver/default_policy_test.rs @@ -1,17 +1,23 @@ //! ref: composer/tests/Composer/Test/DependencyResolver/DefaultPolicyTest.php use indexmap::IndexMap; +use shirabe::dependency_resolver::PolicyInterface; use shirabe::dependency_resolver::default_policy::DefaultPolicy; +use shirabe::package::handle::{CompleteAliasPackageHandle, CompletePackageHandle}; use shirabe::repository::array_repository::ArrayRepository; +use shirabe::repository::handle::{LockArrayRepositoryHandle, RepositoryInterfaceHandle}; use shirabe::repository::lock_array_repository::LockArrayRepository; use shirabe::repository::repository_set::RepositorySet; use shirabe::util::platform::Platform; +use shirabe_semver::constraint::{AnyConstraint, SimpleConstraint}; + +use crate::test_case::get_package; #[allow(dead_code)] struct Fixtures { repository_set: RepositorySet, repo: ArrayRepository, - repo_locked: LockArrayRepository, + repo_locked: LockArrayRepositoryHandle, policy: DefaultPolicy, } @@ -25,7 +31,7 @@ fn set_up() -> Fixtures { IndexMap::new(), ); let repo = ArrayRepository::new(vec![]).unwrap(); - let repo_locked = LockArrayRepository::new(vec![]).unwrap(); + let repo_locked = LockArrayRepositoryHandle::new(LockArrayRepository::new(vec![]).unwrap()); let policy = DefaultPolicy::new(false, false, None); @@ -48,141 +54,558 @@ impl Drop for TearDown { } } -// These build a Pool from packages and exercise DefaultPolicy::selectPreferredPackages. -// Constructing the packages/constraints parses versions through a look-around regex the -// regex crate cannot compile, and the setup mirrors the solver fixtures. +#[ignore] #[test] -#[ignore = "not yet ported (DefaultPolicy over a Pool; constraint parsing uses a look-around regex)"] fn test_select_single() { let _tear_down = TearDown; - let _fixtures = set_up(); - todo!() + let mut fixtures = set_up(); + + let package_a = get_package("A", "1.0"); + fixtures.repo.add_package(package_a.clone()).unwrap(); + fixtures + .repository_set + .add_repository(RepositoryInterfaceHandle::new(fixtures.repo)) + .unwrap(); + + let pool = fixtures + .repository_set + .create_pool_for_package("A", Some(fixtures.repo_locked.clone())) + .unwrap(); + + let literals = vec![package_a.get_id()]; + let expected = vec![package_a.get_id()]; + + let selected = fixtures + .policy + .select_preferred_packages(&pool, literals, None); + + assert_eq!(expected, selected); } +#[ignore] #[test] -#[ignore = "not yet ported (DefaultPolicy over a Pool; constraint parsing uses a look-around regex)"] fn test_select_newest() { let _tear_down = TearDown; - let _fixtures = set_up(); - todo!() + let mut fixtures = set_up(); + + let package_a1 = get_package("A", "1.0"); + let package_a2 = get_package("A", "2.0"); + fixtures.repo.add_package(package_a1.clone()).unwrap(); + fixtures.repo.add_package(package_a2.clone()).unwrap(); + fixtures + .repository_set + .add_repository(RepositoryInterfaceHandle::new(fixtures.repo)) + .unwrap(); + + let pool = fixtures + .repository_set + .create_pool_for_package("A", Some(fixtures.repo_locked.clone())) + .unwrap(); + + let literals = vec![package_a1.get_id(), package_a2.get_id()]; + let expected = vec![package_a2.get_id()]; + + let selected = fixtures + .policy + .select_preferred_packages(&pool, literals, None); + + assert_eq!(expected, selected); } +#[ignore] #[test] -#[ignore = "not yet ported (DefaultPolicy over a Pool; constraint parsing uses a look-around regex)"] fn test_select_newest_picks_latest() { let _tear_down = TearDown; - let _fixtures = set_up(); - todo!() + let mut fixtures = set_up(); + + let package_a1 = get_package("A", "1.0.0"); + let package_a2 = get_package("A", "1.0.1-alpha"); + fixtures.repo.add_package(package_a1.clone()).unwrap(); + fixtures.repo.add_package(package_a2.clone()).unwrap(); + fixtures + .repository_set + .add_repository(RepositoryInterfaceHandle::new(fixtures.repo)) + .unwrap(); + + let pool = fixtures + .repository_set + .create_pool_for_package("A", Some(fixtures.repo_locked.clone())) + .unwrap(); + + let literals = vec![package_a1.get_id(), package_a2.get_id()]; + let expected = vec![package_a2.get_id()]; + + let selected = fixtures + .policy + .select_preferred_packages(&pool, literals, None); + + assert_eq!(expected, selected); } +#[ignore] #[test] -#[ignore = "not yet ported (DefaultPolicy over a Pool; constraint parsing uses a look-around regex)"] fn test_select_newest_picks_latest_stable_with_prefer_stable() { let _tear_down = TearDown; - let _fixtures = set_up(); - todo!() + let mut fixtures = set_up(); + + let package_a1 = get_package("A", "1.0.0"); + let package_a2 = get_package("A", "1.0.1-alpha"); + fixtures.repo.add_package(package_a1.clone()).unwrap(); + fixtures.repo.add_package(package_a2.clone()).unwrap(); + fixtures + .repository_set + .add_repository(RepositoryInterfaceHandle::new(fixtures.repo)) + .unwrap(); + + let pool = fixtures + .repository_set + .create_pool_for_package("A", Some(fixtures.repo_locked.clone())) + .unwrap(); + + let literals = vec![package_a1.get_id(), package_a2.get_id()]; + let expected = vec![package_a1.get_id()]; + + let policy = DefaultPolicy::new(true, false, None); + let selected = policy.select_preferred_packages(&pool, literals, None); + + assert_eq!(expected, selected); } +#[ignore] #[test] -#[ignore = "not yet ported (DefaultPolicy over a Pool; constraint parsing uses a look-around regex)"] fn test_select_lowest_with_prefer_dev_over_prerelease() { let _tear_down = TearDown; - let _fixtures = set_up(); - todo!() + + for stability in ["alpha1", "beta1", "RC1"] { + let mut fixtures = set_up(); + + Platform::put_env("COMPOSER_PREFER_DEV_OVER_PRERELEASE", "1"); + let dev_package = get_package("A", "dev-master"); + let prerelease_package = get_package("A", &format!("1.0.0-{}", stability)); + fixtures.repo.add_package(dev_package.clone()).unwrap(); + fixtures + .repo + .add_package(prerelease_package.clone()) + .unwrap(); + fixtures + .repository_set + .add_repository(RepositoryInterfaceHandle::new(fixtures.repo)) + .unwrap(); + + let pool = fixtures + .repository_set + .create_pool_for_package("A", Some(fixtures.repo_locked.clone())) + .unwrap(); + + let literals = vec![dev_package.get_id(), prerelease_package.get_id()]; + let expected = vec![dev_package.get_id()]; + + let policy = DefaultPolicy::new(true, true, None); + let selected = policy.select_preferred_packages(&pool, literals, None); + + assert_eq!(expected, selected); + } } +#[ignore] #[test] -#[ignore = "not yet ported (DefaultPolicy over a Pool; constraint parsing uses a look-around regex)"] fn test_select_lowest_prefers_prerelease_over_dev() { let _tear_down = TearDown; - let _fixtures = set_up(); - todo!() + + for stability in ["alpha1", "beta1", "RC1"] { + let mut fixtures = set_up(); + + let dev_package = get_package("A", "dev-master"); + let prerelease_package = get_package("A", &format!("1.0.0-{}", stability)); + fixtures.repo.add_package(dev_package.clone()).unwrap(); + fixtures + .repo + .add_package(prerelease_package.clone()) + .unwrap(); + fixtures + .repository_set + .add_repository(RepositoryInterfaceHandle::new(fixtures.repo)) + .unwrap(); + + let pool = fixtures + .repository_set + .create_pool_for_package("A", Some(fixtures.repo_locked.clone())) + .unwrap(); + + let literals = vec![prerelease_package.get_id(), dev_package.get_id()]; + let expected = vec![prerelease_package.get_id()]; + + let policy = DefaultPolicy::new(true, true, None); + let selected = policy.select_preferred_packages(&pool, literals, None); + + assert_eq!(expected, selected); + } } +#[ignore] #[test] -#[ignore = "not yet ported (DefaultPolicy over a Pool; constraint parsing uses a look-around regex)"] fn test_select_lowest_with_prefer_stable_still_prefers_stable() { let _tear_down = TearDown; - let _fixtures = set_up(); - todo!() + let mut fixtures = set_up(); + + Platform::put_env("COMPOSER_PREFER_DEV_OVER_PRERELEASE", "1"); + let stable_package = get_package("A", "1.0.0"); + let dev_package = get_package("A", "dev-master"); + fixtures.repo.add_package(stable_package.clone()).unwrap(); + fixtures.repo.add_package(dev_package.clone()).unwrap(); + fixtures + .repository_set + .add_repository(RepositoryInterfaceHandle::new(fixtures.repo)) + .unwrap(); + + let pool = fixtures + .repository_set + .create_pool_for_package("A", Some(fixtures.repo_locked.clone())) + .unwrap(); + + let literals = vec![stable_package.get_id(), dev_package.get_id()]; + let expected = vec![stable_package.get_id()]; + + let policy = DefaultPolicy::new(true, true, None); + let selected = policy.select_preferred_packages(&pool, literals, None); + + assert_eq!(expected, selected); } +#[ignore] #[test] -#[ignore = "not yet ported (DefaultPolicy over a Pool; constraint parsing uses a look-around regex)"] fn test_select_newest_with_dev_picks_non_dev() { let _tear_down = TearDown; - let _fixtures = set_up(); - todo!() + let mut fixtures = set_up(); + + let package_a1 = get_package("A", "dev-foo"); + let package_a2 = get_package("A", "1.0.0"); + fixtures.repo.add_package(package_a1.clone()).unwrap(); + fixtures.repo.add_package(package_a2.clone()).unwrap(); + fixtures + .repository_set + .add_repository(RepositoryInterfaceHandle::new(fixtures.repo)) + .unwrap(); + + let pool = fixtures + .repository_set + .create_pool_for_package("A", Some(fixtures.repo_locked.clone())) + .unwrap(); + + let literals = vec![package_a1.get_id(), package_a2.get_id()]; + let expected = vec![package_a2.get_id()]; + + let selected = fixtures + .policy + .select_preferred_packages(&pool, literals, None); + + assert_eq!(expected, selected); } +#[ignore] #[test] -#[ignore = "not yet ported (DefaultPolicy over a Pool; constraint parsing uses a look-around regex)"] fn test_select_newest_with_preferred_version_picks_preferred_version_if_available() { let _tear_down = TearDown; - let _fixtures = set_up(); - todo!() + let mut fixtures = set_up(); + + let package_a1 = get_package("A", "1.0.0"); + let package_a2 = get_package("A", "1.1.0"); + let package_a2b = get_package("A", "1.1.0"); + let package_a3 = get_package("A", "1.2.0"); + fixtures.repo.add_package(package_a1.clone()).unwrap(); + fixtures.repo.add_package(package_a2.clone()).unwrap(); + fixtures.repo.add_package(package_a2b.clone()).unwrap(); + fixtures.repo.add_package(package_a3.clone()).unwrap(); + fixtures + .repository_set + .add_repository(RepositoryInterfaceHandle::new(fixtures.repo)) + .unwrap(); + + let pool = fixtures + .repository_set + .create_pool_for_package("A", Some(fixtures.repo_locked.clone())) + .unwrap(); + + let literals = vec![ + package_a1.get_id(), + package_a2.get_id(), + package_a2b.get_id(), + package_a3.get_id(), + ]; + let expected = vec![package_a2.get_id(), package_a2b.get_id()]; + + let mut preferred = IndexMap::new(); + preferred.insert("a".to_string(), "1.1.0.0".to_string()); + let policy = DefaultPolicy::new(false, false, Some(preferred)); + let selected = policy.select_preferred_packages(&pool, literals, None); + + assert_eq!(expected, selected); } +#[ignore] #[test] -#[ignore = "not yet ported (DefaultPolicy over a Pool; constraint parsing uses a look-around regex)"] fn test_select_newest_with_preferred_version_picks_newest_otherwise() { let _tear_down = TearDown; - let _fixtures = set_up(); - todo!() + let mut fixtures = set_up(); + + let package_a1 = get_package("A", "1.0.0"); + let package_a2 = get_package("A", "1.2.0"); + fixtures.repo.add_package(package_a1.clone()).unwrap(); + fixtures.repo.add_package(package_a2.clone()).unwrap(); + fixtures + .repository_set + .add_repository(RepositoryInterfaceHandle::new(fixtures.repo)) + .unwrap(); + + let pool = fixtures + .repository_set + .create_pool_for_package("A", Some(fixtures.repo_locked.clone())) + .unwrap(); + + let literals = vec![package_a1.get_id(), package_a2.get_id()]; + let expected = vec![package_a2.get_id()]; + + let mut preferred = IndexMap::new(); + preferred.insert("a".to_string(), "1.1.0.0".to_string()); + let policy = DefaultPolicy::new(false, false, Some(preferred)); + let selected = policy.select_preferred_packages(&pool, literals, None); + + assert_eq!(expected, selected); } +#[ignore] #[test] -#[ignore = "not yet ported (DefaultPolicy over a Pool; constraint parsing uses a look-around regex)"] fn test_select_newest_with_preferred_version_picks_lowest_if_prefer_lowest() { let _tear_down = TearDown; - let _fixtures = set_up(); - todo!() + let mut fixtures = set_up(); + + let package_a1 = get_package("A", "1.0.0"); + let package_a2 = get_package("A", "1.2.0"); + fixtures.repo.add_package(package_a1.clone()).unwrap(); + fixtures.repo.add_package(package_a2.clone()).unwrap(); + fixtures + .repository_set + .add_repository(RepositoryInterfaceHandle::new(fixtures.repo)) + .unwrap(); + + let pool = fixtures + .repository_set + .create_pool_for_package("A", Some(fixtures.repo_locked.clone())) + .unwrap(); + + let literals = vec![package_a1.get_id(), package_a2.get_id()]; + let expected = vec![package_a1.get_id()]; + + let mut preferred = IndexMap::new(); + preferred.insert("a".to_string(), "1.1.0.0".to_string()); + let policy = DefaultPolicy::new(false, true, Some(preferred)); + let selected = policy.select_preferred_packages(&pool, literals, None); + + assert_eq!(expected, selected); } +#[ignore] #[test] -#[ignore = "not yet ported (DefaultPolicy over a Pool; constraint parsing uses a look-around regex)"] fn test_repository_ordering_affects_priority() { let _tear_down = TearDown; - let _fixtures = set_up(); - todo!() + let mut fixtures = set_up(); + + let repo1 = ArrayRepository::new(vec![]).unwrap(); + let repo2 = ArrayRepository::new(vec![]).unwrap(); + + let package1 = get_package("A", "1.0"); + let package2 = get_package("A", "1.1"); + let package3 = get_package("A", "1.1"); + let package4 = get_package("A", "1.2"); + repo1.add_package(package1.clone()).unwrap(); + repo1.add_package(package2.clone()).unwrap(); + repo2.add_package(package3.clone()).unwrap(); + repo2.add_package(package4.clone()).unwrap(); + + let repo1_handle = RepositoryInterfaceHandle::new(repo1); + let repo2_handle = RepositoryInterfaceHandle::new(repo2); + + fixtures + .repository_set + .add_repository(repo1_handle.clone()) + .unwrap(); + fixtures + .repository_set + .add_repository(repo2_handle.clone()) + .unwrap(); + + let pool = fixtures + .repository_set + .create_pool_for_package("A", Some(fixtures.repo_locked.clone())) + .unwrap(); + + let literals = vec![ + package1.get_id(), + package2.get_id(), + package3.get_id(), + package4.get_id(), + ]; + let expected = vec![package2.get_id()]; + let selected = fixtures + .policy + .select_preferred_packages(&pool, literals.clone(), None); + + assert_eq!(expected, selected); + + let mut repository_set = RepositorySet::new( + "dev", + IndexMap::new(), + vec![], + IndexMap::new(), + IndexMap::new(), + IndexMap::new(), + ); + repository_set.add_repository(repo2_handle).unwrap(); + repository_set.add_repository(repo1_handle).unwrap(); + + let pool = repository_set + .create_pool_for_package("A", Some(fixtures.repo_locked.clone())) + .unwrap(); + + let expected = vec![package4.get_id()]; + let selected = fixtures + .policy + .select_preferred_packages(&pool, literals, None); + + assert_eq!(expected, selected); } +#[ignore] #[test] -#[ignore = "not yet ported (DefaultPolicy over a Pool; constraint parsing uses a look-around regex)"] fn test_select_local_repos_first() { let _tear_down = TearDown; - let _fixtures = set_up(); - todo!() + let mut fixtures = set_up(); + + let repo_important = ArrayRepository::new(vec![]).unwrap(); + + let package_a = get_package("A", "dev-master"); + let package_a_alias = CompleteAliasPackageHandle::new( + CompletePackageHandle::from_rc_unchecked(package_a.as_rc().clone()), + "2.1.9999999.9999999-dev".to_string(), + "2.1.x-dev".to_string(), + ); + let package_a_important = get_package("A", "dev-feature-a"); + let package_a_alias_important = CompleteAliasPackageHandle::new( + CompletePackageHandle::from_rc_unchecked(package_a_important.as_rc().clone()), + "2.1.9999999.9999999-dev".to_string(), + "2.1.x-dev".to_string(), + ); + let package_a2_important = get_package("A", "dev-master"); + let package_a2_alias_important = CompleteAliasPackageHandle::new( + CompletePackageHandle::from_rc_unchecked(package_a2_important.as_rc().clone()), + "2.1.9999999.9999999-dev".to_string(), + "2.1.x-dev".to_string(), + ); + package_a_alias_important.set_root_package_alias(true); + + fixtures.repo.add_package(package_a).unwrap(); + fixtures + .repo + .add_package(package_a_alias.clone().into()) + .unwrap(); + repo_important.add_package(package_a_important).unwrap(); + repo_important + .add_package(package_a_alias_important.clone().into()) + .unwrap(); + repo_important.add_package(package_a2_important).unwrap(); + repo_important + .add_package(package_a2_alias_important.into()) + .unwrap(); + + fixtures + .repository_set + .add_repository(RepositoryInterfaceHandle::new(repo_important)) + .unwrap(); + fixtures + .repository_set + .add_repository(RepositoryInterfaceHandle::new(fixtures.repo)) + .unwrap(); + fixtures + .repository_set + .add_repository(fixtures.repo_locked.clone().into()) + .unwrap(); + + let mut pool = fixtures + .repository_set + .create_pool_for_package("A", Some(fixtures.repo_locked.clone())) + .unwrap(); + + let constraint = AnyConstraint::Simple(SimpleConstraint::new( + "=".to_string(), + "2.1.9999999.9999999-dev".to_string(), + None, + )); + let packages = pool.what_provides("a", Some(&constraint)); + assert!(!packages.is_empty()); + let mut literals = vec![]; + for package in &packages { + literals.push(package.get_id()); + } + + let expected = vec![package_a_alias_important.get_id()]; + + let selected = fixtures + .policy + .select_preferred_packages(&pool, literals, None); + + assert_eq!(expected, selected); } +#[ignore = "set_provides/set_replaces only exist on RootPackage handles; CompletePackage from get_package has no set_provides"] #[test] -#[ignore = "not yet ported (DefaultPolicy over a Pool; constraint parsing uses a look-around regex)"] fn test_select_all_providers() { let _tear_down = TearDown; let _fixtures = set_up(); todo!() } +#[ignore = "set_provides/set_replaces only exist on RootPackage handles; CompletePackage from get_package has no set_replaces"] #[test] -#[ignore = "not yet ported (DefaultPolicy over a Pool; constraint parsing uses a look-around regex)"] fn test_prefer_non_replacing_from_same_repo() { let _tear_down = TearDown; let _fixtures = set_up(); todo!() } +#[ignore = "set_replaces only exists on RootPackage handles; CompletePackage from get_package has no set_replaces"] #[test] -#[ignore = "not yet ported (DefaultPolicy over a Pool; constraint parsing uses a look-around regex)"] fn test_prefer_replacing_package_from_same_vendor() { let _tear_down = TearDown; let _fixtures = set_up(); todo!() } +#[ignore] #[test] -#[ignore = "not yet ported (DefaultPolicy over a Pool; constraint parsing uses a look-around regex)"] fn test_select_lowest() { let _tear_down = TearDown; - let _fixtures = set_up(); - todo!() + let mut fixtures = set_up(); + + let policy = DefaultPolicy::new(false, true, None); + + let package_a1 = get_package("A", "1.0"); + let package_a2 = get_package("A", "2.0"); + fixtures.repo.add_package(package_a1.clone()).unwrap(); + fixtures.repo.add_package(package_a2.clone()).unwrap(); + fixtures + .repository_set + .add_repository(RepositoryInterfaceHandle::new(fixtures.repo)) + .unwrap(); + + let pool = fixtures + .repository_set + .create_pool_for_package("A", Some(fixtures.repo_locked.clone())) + .unwrap(); + + let literals = vec![package_a1.get_id(), package_a2.get_id()]; + let expected = vec![package_a1.get_id()]; + + let selected = policy.select_preferred_packages(&pool, literals, None); + + assert_eq!(expected, selected); } diff --git a/crates/shirabe/tests/dependency_resolver/pool_builder_test.rs b/crates/shirabe/tests/dependency_resolver/pool_builder_test.rs index 617c7b9..4c72091 100644 --- a/crates/shirabe/tests/dependency_resolver/pool_builder_test.rs +++ b/crates/shirabe/tests/dependency_resolver/pool_builder_test.rs @@ -3,7 +3,7 @@ // testPoolBuilder is a large fixture-driven case that loads packages from test inputs and // builds a Pool; constraint parsing uses a look-around regex the regex crate cannot compile. #[test] -#[ignore = "not yet ported (fixture-driven PoolBuilder; constraint parsing uses a look-around regex)"] +#[ignore = "ArrayLoader::load (single-package, pub) and Pool::count are not exposed: the loadPackage closure calls $loader->load($data) per package and getPackageResultSet uses count($pool)"] fn test_pool_builder() { todo!() } diff --git a/crates/shirabe/tests/dependency_resolver/pool_optimizer_test.rs b/crates/shirabe/tests/dependency_resolver/pool_optimizer_test.rs index a25aec6..408c191 100644 --- a/crates/shirabe/tests/dependency_resolver/pool_optimizer_test.rs +++ b/crates/shirabe/tests/dependency_resolver/pool_optimizer_test.rs @@ -1,9 +1,264 @@ //! ref: composer/tests/Composer/Test/DependencyResolver/PoolOptimizerTest.php -// testPoolOptimizer is a large fixture-driven case building and optimizing a Pool; -// constraint parsing uses a look-around regex the regex crate cannot compile. +use std::path::PathBuf; +use std::rc::Rc; + +use indexmap::IndexMap; +use shirabe::dependency_resolver::default_policy::DefaultPolicy; +use shirabe::dependency_resolver::pool::Pool; +use shirabe::dependency_resolver::pool_optimizer::PoolOptimizer; +use shirabe::dependency_resolver::request::Request; +use shirabe::json::JsonFile; +use shirabe::package::BasePackageHandle; +use shirabe::package::loader::{ArrayLoader, LoaderInterface}; +use shirabe::package::version::version_parser::VersionParser; +use shirabe::repository::handle::LockArrayRepositoryHandle; +use shirabe::repository::lock_array_repository::LockArrayRepository; +use shirabe_external_packages::composer::pcre::preg::Preg; +use shirabe_php_shim::PREG_SPLIT_DELIM_CAPTURE; +use shirabe_php_shim::PhpMixed; + +fn load_package(package_data: &PhpMixed) -> BasePackageHandle { + let loader = ArrayLoader::new(None, false); + loader + .load(package_data.as_array().unwrap().clone(), None) + .unwrap() +} + +fn load_packages(packages_data: &[PhpMixed]) -> Vec<BasePackageHandle> { + let mut packages: Vec<BasePackageHandle> = Vec::new(); + + for package_data in packages_data { + let package = load_package(package_data); + packages.push(package.clone()); + if let Some(alias) = package.as_alias() { + packages.push(alias.get_alias_of().into()); + } + } + + packages +} + +fn reduce_packages_info_for_comparison(packages: &[BasePackageHandle]) -> Vec<String> { + let mut packages_info: Vec<String> = Vec::new(); + + for package in packages { + let suffix = if let Some(alias) = package.as_alias() { + format!(" (alias of {})", alias.get_alias_of().get_version()) + } else { + String::new() + }; + packages_info.push(format!( + "{}@{}{}", + package.get_name(), + package.get_version(), + suffix + )); + } + + packages_info.sort(); + + packages_info +} + +fn read_test_file(file: &str, fixtures_dir: &str) -> IndexMap<String, String> { + let contents = shirabe_php_shim::file_get_contents(file).unwrap(); + let tokens = Preg::split4( + r"#(?:^|\n*)--([A-Z-]+)--\n#", + &contents, + -1, + PREG_SPLIT_DELIM_CAPTURE, + ); + + let section_info: Vec<&str> = vec!["TEST", "REQUEST", "POOL-BEFORE", "POOL-AFTER"]; + + let mut section: Option<String> = None; + let mut data: IndexMap<String, String> = IndexMap::new(); + for token in tokens { + if section.is_none() && token.is_empty() { + continue; + } + + if section.is_none() { + if !section_info.contains(&token.as_str()) { + panic!( + "The test file \"{}\" must not contain a section named \"{}\".", + file.replace(&format!("{}/", fixtures_dir), ""), + token + ); + } + section = Some(token); + continue; + } + + let section_data = token; + data.insert(section.take().unwrap(), section_data); + } + + for required_section in §ion_info { + if !data.contains_key(*required_section) { + panic!( + "The test file \"{}\" must have a section named \"{}\".", + file.replace(&format!("{}/", fixtures_dir), ""), + required_section + ); + } + } + + data +} + +fn collect_test_files(dir: &std::path::Path, out: &mut Vec<PathBuf>) { + for entry in std::fs::read_dir(dir).unwrap() { + let entry = entry.unwrap(); + let path = entry.path(); + if path.is_dir() { + collect_test_files(&path, out); + } else { + out.push(path); + } + } +} + +fn provide_integration_tests() -> IndexMap< + String, + ( + PhpMixed, + Vec<BasePackageHandle>, + Vec<BasePackageHandle>, + String, + ), +> { + let fixtures_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR")) + .join("../../composer/tests/Composer/Test/DependencyResolver/Fixtures/pooloptimizer"); + let fixtures_dir = std::fs::canonicalize(&fixtures_dir).unwrap(); + let fixtures_dir_str = fixtures_dir.to_str().unwrap().to_string(); + + let mut files: Vec<PathBuf> = Vec::new(); + collect_test_files(&fixtures_dir, &mut files); + + let mut tests: IndexMap< + String, + ( + PhpMixed, + Vec<BasePackageHandle>, + Vec<BasePackageHandle>, + String, + ), + > = IndexMap::new(); + for file in files { + let file = file.to_str().unwrap().to_string(); + + if !Preg::is_match(r"/\.test$/", &file) { + continue; + } + + let test_data = read_test_file(&file, &fixtures_dir_str); + let message = test_data["TEST"].clone(); + let request_data = JsonFile::parse_json(Some(&test_data["REQUEST"]), None).unwrap(); + let packages_before = load_packages( + JsonFile::parse_json(Some(&test_data["POOL-BEFORE"]), None) + .unwrap() + .as_list() + .unwrap(), + ); + let expected_packages = load_packages( + JsonFile::parse_json(Some(&test_data["POOL-AFTER"]), None) + .unwrap() + .as_list() + .unwrap(), + ); + + let basename = std::path::Path::new(&file) + .file_name() + .unwrap() + .to_str() + .unwrap() + .to_string(); + tests.insert( + basename, + (request_data, packages_before, expected_packages, message), + ); + } + + tests +} + +fn run_test_pool_optimizer( + request_data: &PhpMixed, + packages_before: Vec<BasePackageHandle>, + expected_packages: &[BasePackageHandle], + message: &str, +) { + let request_data = request_data.as_array().unwrap(); + + let locked_repo = LockArrayRepositoryHandle::new(LockArrayRepository::new(vec![]).unwrap()); + + let mut request = Request::new(Some(locked_repo)); + let parser = VersionParser::new(); + + if let Some(locked) = request_data.get("locked") { + for package in locked.as_list().unwrap() { + request.lock_package(load_package(package)); + } + } + if let Some(fixed) = request_data.get("fixed") { + for package in fixed.as_list().unwrap() { + request.fix_package(load_package(package)); + } + } + + for (package, constraint) in request_data["require"].as_array().unwrap() { + request + .require_name( + package, + Some( + parser + .parse_constraints(constraint.as_string().unwrap()) + .unwrap(), + ), + ) + .unwrap(); + } + + let prefer_stable = request_data + .get("preferStable") + .and_then(|v| v.as_bool()) + .unwrap_or(false); + let prefer_lowest = request_data + .get("preferLowest") + .and_then(|v| v.as_bool()) + .unwrap_or(false); + + let pool = Pool::new( + packages_before, + vec![], + IndexMap::new(), + IndexMap::new(), + IndexMap::new(), + IndexMap::new(), + ); + let mut pool_optimizer = PoolOptimizer::new(Rc::new(DefaultPolicy::new( + prefer_stable, + prefer_lowest, + None, + ))); + + let pool = pool_optimizer.optimize(&request, &pool); + + assert_eq!( + reduce_packages_info_for_comparison(expected_packages), + reduce_packages_info_for_comparison(pool.get_packages()), + "{}", + message + ); +} + +#[ignore] #[test] -#[ignore = "not yet ported (fixture-driven PoolOptimizer; constraint parsing uses a look-around regex)"] fn test_pool_optimizer() { - todo!() + let tests = provide_integration_tests(); + for (_name, (request_data, packages_before, expected_packages, message)) in tests { + run_test_pool_optimizer(&request_data, packages_before, &expected_packages, &message); + } } diff --git a/crates/shirabe/tests/dependency_resolver/rule_set_test.rs b/crates/shirabe/tests/dependency_resolver/rule_set_test.rs index 5620b2a..f1e573e 100644 --- a/crates/shirabe/tests/dependency_resolver/rule_set_test.rs +++ b/crates/shirabe/tests/dependency_resolver/rule_set_test.rs @@ -3,10 +3,14 @@ use std::cell::RefCell; use std::rc::Rc; +use indexmap::IndexMap; use shirabe::dependency_resolver::{ - GenericRule, RULE_LEARNED, RULE_ROOT_REQUIRE, ReasonData, Rule, RuleSet, + GenericRule, Pool, RULE_LEARNED, RULE_ROOT_REQUIRE, ReasonData, Request, Rule, RuleSet, }; -use shirabe_semver::constraint::MatchAllConstraint; +use shirabe::repository::RepositorySet; +use shirabe_semver::constraint::{MatchAllConstraint, MatchNoneConstraint}; + +use crate::test_case::get_package; fn root_require_reason() -> ReasonData { ReasonData::RootRequire { @@ -156,10 +160,54 @@ fn test_get_iterator_without() { assert!(Rc::ptr_eq(&iterator.current(), &rule2)); } -// In PHP this mocks RepositorySet and Request to build the pretty string. The mocked -// collaborators cannot be reproduced here, and add() also reaches hash_raw (todo!()). +// The constraint is MatchNoneConstraint, so what_provides returns no packages and the +// "No package found" branch is taken; the RepositorySet/Request collaborators are never +// actually consulted (PHP mocks them with the constructor disabled). #[test] -#[ignore = "getPrettyString needs mocked RepositorySet/Request, and add() reaches hash_raw (todo!())"] +#[ignore] fn test_pretty_string() { - todo!() + let p = get_package("foo", "2.1"); + let mut pool = Pool::new( + vec![p.clone()], + vec![], + IndexMap::new(), + IndexMap::new(), + IndexMap::new(), + IndexMap::new(), + ); + + let repository_set = RepositorySet::new( + "stable", + IndexMap::new(), + vec![], + IndexMap::new(), + IndexMap::new(), + IndexMap::new(), + ); + let request = Request::new(None); + + let mut rule_set = RuleSet::new(); + let literal = p.get_id(); + let rule = Rc::new(RefCell::new(Rule::Generic(GenericRule::new( + vec![literal], + RULE_ROOT_REQUIRE, + ReasonData::RootRequire { + package_name: "foo/bar".to_string(), + constraint: MatchNoneConstraint::new(None).into(), + }, + )))); + + rule_set.add(rule, RuleSet::TYPE_REQUEST).unwrap(); + + let pretty = rule_set + .get_pretty_string( + Some(&repository_set), + Some(&request), + Some(&mut pool), + false, + ) + .unwrap(); + assert!( + pretty.contains("REQUEST : No package found to satisfy root composer.json require foo/bar") + ); } diff --git a/crates/shirabe/tests/dependency_resolver/rule_test.rs b/crates/shirabe/tests/dependency_resolver/rule_test.rs index 9806820..156931c 100644 --- a/crates/shirabe/tests/dependency_resolver/rule_test.rs +++ b/crates/shirabe/tests/dependency_resolver/rule_test.rs @@ -1,8 +1,16 @@ //! ref: composer/tests/Composer/Test/DependencyResolver/RuleTest.php -use shirabe::dependency_resolver::{GenericRule, RULE_ROOT_REQUIRE, ReasonData, Rule, RuleSet}; +use indexmap::IndexMap; +use shirabe::dependency_resolver::{ + GenericRule, Pool, RULE_PACKAGE_REQUIRES, RULE_ROOT_REQUIRE, ReasonData, Request, Rule, RuleSet, +}; +use shirabe::package::Link; +use shirabe::repository::RepositorySet; +use shirabe_php_shim::{PHP_VERSION_ID, hash_raw, unpack}; use shirabe_semver::constraint::MatchAllConstraint; +use crate::test_case::get_package; + fn root_require_reason() -> ReasonData { ReasonData::RootRequire { package_name: String::new(), @@ -18,10 +26,23 @@ fn generic_rule(literals: Vec<i64>) -> Rule { )) } +#[ignore] #[test] -#[ignore = "Rule::get_hash reaches shirabe_php_shim::hash_raw, which is todo!()"] fn test_get_hash() { - todo!() + let rule = generic_rule(vec![123]); + + let algo = if PHP_VERSION_ID > 80100 { + "xxh3" + } else { + "sha1" + }; + let binary = hash_raw(algo, "123"); + let hash = unpack("ihash", &binary).unwrap(); + + assert_eq!( + hash.get("hash").unwrap().as_int(), + rule.get_hash().unwrap().as_int() + ); } #[test] @@ -85,10 +106,56 @@ fn test_is_assertions() { assert!(rule2.is_assertion()); } -// In PHP this mocks RepositorySet and Request and passes a Link reason to build the -// pretty string. The mocked collaborators cannot be reproduced here. +// PHP mocks RepositorySet and Request with the constructor disabled; the RULE_PACKAGE_REQUIRES +// branch with a non-empty requires list never consults them, so real minimal instances suffice. +#[ignore] #[test] -#[ignore = "getPrettyString needs mocked RepositorySet and Request; mocking is not available"] fn test_pretty_string() { - todo!() + let p1 = get_package("foo", "2.1"); + let p2 = get_package("baz", "1.1"); + let mut pool = Pool::new( + vec![p1.clone(), p2.clone()], + vec![], + IndexMap::new(), + IndexMap::new(), + IndexMap::new(), + IndexMap::new(), + ); + + let repository_set = RepositorySet::new( + "stable", + IndexMap::new(), + vec![], + IndexMap::new(), + IndexMap::new(), + IndexMap::new(), + ); + let request = Request::new(None); + + let empty_constraint = MatchAllConstraint::new(Some("*".to_string())); + + let rule = Rule::Generic(GenericRule::new( + vec![p1.get_id(), -p2.get_id()], + RULE_PACKAGE_REQUIRES, + ReasonData::Link(Link::new( + "baz".to_string(), + "foo".to_string(), + empty_constraint.into(), + None, + "*".to_string(), + )), + )); + + assert_eq!( + "baz 1.1 relates to foo * -> satisfiable by foo[2.1].", + rule.get_pretty_string( + &repository_set, + &request, + &mut pool, + false, + &IndexMap::new(), + &vec![], + ) + .unwrap() + ); } diff --git a/crates/shirabe/tests/dependency_resolver/security_advisory_pool_filter_test.rs b/crates/shirabe/tests/dependency_resolver/security_advisory_pool_filter_test.rs index 825d6b4..1232241 100644 --- a/crates/shirabe/tests/dependency_resolver/security_advisory_pool_filter_test.rs +++ b/crates/shirabe/tests/dependency_resolver/security_advisory_pool_filter_test.rs @@ -5,26 +5,105 @@ // crate cannot compile. The fixtures also build PackageRepository security-advisory data // and run the Auditor. +use indexmap::IndexMap; +use shirabe::advisory::AuditConfig; +use shirabe::advisory::Auditor; +use shirabe::dependency_resolver::SecurityAdvisoryPoolFilter; +use shirabe::dependency_resolver::pool::Pool; +use shirabe::dependency_resolver::request::Request; +use shirabe::package::handle::{CompletePackageHandle, PackageHandle, PackageInterfaceHandle}; +use shirabe_php_shim::PhpMixed; + #[test] -#[ignore = "filtering parses affectedVersions via a look-around regex the regex crate cannot compile"] +#[ignore = "requires PackageRepository to implement RepositoryInterface (filter takes Vec<RepositoryInterfaceHandle>) and php-shim uniqid() used by generateSecurityAdvisory; neither exists"] fn test_filter_packages_by_advisories() { todo!() } #[test] -#[ignore = "filtering parses affectedVersions via a look-around regex the regex crate cannot compile"] +#[ignore = "requires PackageRepository to implement RepositoryInterface (filter takes Vec<RepositoryInterfaceHandle>) and php-shim uniqid() used by generateSecurityAdvisory; neither exists"] fn test_dont_filter_packages_by_ignored_advisories() { todo!() } #[test] -#[ignore = "filtering parses affectedVersions via a look-around regex the regex crate cannot compile"] +#[ignore = "requires PackageRepository to implement RepositoryInterface (filter takes Vec<RepositoryInterfaceHandle>) and php-shim uniqid() used by generateSecurityAdvisory; neither exists"] fn test_dont_filter_packages_with_block_insecure_disabled() { todo!() } +#[ignore] #[test] -#[ignore = "filtering parses affectedVersions via a look-around regex the regex crate cannot compile"] fn test_dont_filter_packages_with_abandoned_package() { - todo!() + let package_name_ignore_abandoned = "acme/ignore-abandoned"; + let mut ignore_abandoned: IndexMap<String, Option<String>> = IndexMap::new(); + ignore_abandoned.insert(package_name_ignore_abandoned.to_string(), None); + let audit_config = AuditConfig::new( + true, + Auditor::FORMAT_SUMMARY.to_string(), + Auditor::ABANDONED_FAIL.to_string(), + true, + true, + false, + IndexMap::new(), + IndexMap::new(), + IndexMap::new(), + IndexMap::new(), + ignore_abandoned.clone(), + ignore_abandoned, + ); + let filter = SecurityAdvisoryPoolFilter::new(Auditor, audit_config); + + let abandoned_package = CompletePackageHandle::new( + "acme/package".to_string(), + "1.0.0.0".to_string(), + "1.0".to_string(), + ); + abandoned_package.set_abandoned(PhpMixed::Bool(true)); + let ignore_abandoned_package = CompletePackageHandle::new( + package_name_ignore_abandoned.to_string(), + "1.0.0.0".to_string(), + "1.0".to_string(), + ); + ignore_abandoned_package.set_abandoned(PhpMixed::Bool(true)); + let expected_package = PackageHandle::new( + "acme/other".to_string(), + "1.1.0.0".to_string(), + "1.1".to_string(), + ); + + let expected_package: PackageInterfaceHandle = expected_package.into(); + let abandoned_package: PackageInterfaceHandle = abandoned_package.into(); + let ignore_abandoned_package: PackageInterfaceHandle = ignore_abandoned_package.into(); + + let pool = Pool::new( + vec![ + expected_package.clone(), + abandoned_package.clone(), + ignore_abandoned_package.clone(), + ], + vec![], + IndexMap::new(), + IndexMap::new(), + IndexMap::new(), + IndexMap::new(), + ); + let filtered_pool = filter.filter(pool, vec![], &Request::new(None)).unwrap(); + + let packages = filtered_pool.get_packages(); + assert_eq!(packages.len(), 2); + assert!(packages[0].ptr_eq(&expected_package)); + assert!(packages[1].ptr_eq(&ignore_abandoned_package)); + assert_eq!( + filtered_pool + .get_all_abandoned_removed_package_versions() + .len(), + 1 + ); + assert_eq!( + filtered_pool + .get_all_security_removed_package_versions() + .len(), + 0 + ); } diff --git a/crates/shirabe/tests/dependency_resolver/solver_test.rs b/crates/shirabe/tests/dependency_resolver/solver_test.rs index b876c99..26f1312 100644 --- a/crates/shirabe/tests/dependency_resolver/solver_test.rs +++ b/crates/shirabe/tests/dependency_resolver/solver_test.rs @@ -1,13 +1,22 @@ //! ref: composer/tests/Composer/Test/DependencyResolver/SolverTest.php +use std::cell::RefCell; +use std::rc::Rc; + use indexmap::IndexMap; +use shirabe::dependency_resolver::PolicyInterface; use shirabe::dependency_resolver::default_policy::DefaultPolicy; use shirabe::dependency_resolver::request::Request; +use shirabe::io::io_interface::IOInterface; +use shirabe::io::null_io::NullIO; +use shirabe::package::handle::PackageInterfaceHandle; use shirabe::repository::array_repository::ArrayRepository; -use shirabe::repository::handle::LockArrayRepositoryHandle; +use shirabe::repository::handle::{LockArrayRepositoryHandle, RepositoryInterfaceHandle}; use shirabe::repository::lock_array_repository::LockArrayRepository; use shirabe::repository::repository_set::RepositorySet; +use crate::test_case::{get_alias_package, get_package, get_version_constraint}; + #[allow(dead_code)] struct Fixtures { repo_set: RepositorySet, @@ -41,285 +50,657 @@ fn set_up() -> Fixtures { } } -// These run the dependency Solver over packages/requests built from version constraints, -// whose parsing goes through a look-around regex the regex crate cannot compile; the setup -// also mirrors the larger solver fixtures. +/// One expected solver job. Mirrors the PHP `['job' => ..., 'package'|'from'|'to' => ...]` rows. +enum ExpectedJob { + Single { + job: &'static str, + package: PackageInterfaceHandle, + }, + Update { + from: PackageInterfaceHandle, + to: PackageInterfaceHandle, + }, +} + +/// ref: SolverTest::checkSolverResult (with reposComplete + createSolver folded in). +fn check_solver_result( + mut repo_set: RepositorySet, + repo: ArrayRepository, + repo_locked: LockArrayRepositoryHandle, + mut request: Request, + expected: Vec<ExpectedJob>, +) { + // reposComplete() + repo_set + .add_repository(RepositoryInterfaceHandle::new(repo)) + .unwrap(); + repo_set.add_repository(repo_locked.into()).unwrap(); + + // createSolver() + let io: Rc<RefCell<dyn IOInterface>> = Rc::new(RefCell::new(NullIO::new())); + let pool = repo_set + .create_pool(&mut request, io.clone(), None, None, vec![], None, None) + .unwrap(); + let policy: Rc<dyn PolicyInterface> = Rc::new(DefaultPolicy::new(false, false, None)); + let mut solver = + shirabe::dependency_resolver::solver::Solver::new(policy, Rc::new(RefCell::new(pool)), io); + + let transaction = solver.solve(&request, None).unwrap(); + + // Build readable (unique-name) and identity (ptr) representations of the result and + // the expectation, mirroring the dual assertEquals in the PHP helper. + let mut result_readable: Vec<(String, String)> = Vec::new(); + let mut result_ids: Vec<(String, Vec<usize>)> = Vec::new(); + for operation in transaction.get_operations() { + if let Some(update) = operation.as_update_operation() { + let from = update.get_initial_package(); + let to = update.get_target_package(); + result_readable.push(( + "update".to_string(), + format!("{} => {}", from.get_unique_name(), to.get_unique_name()), + )); + result_ids.push(("update".to_string(), vec![from.ptr_id(), to.ptr_id()])); + } else { + let op_type = operation.get_operation_type(); + let job = match op_type.as_str() { + "markAliasInstalled" => "markAliasInstalled", + "markAliasUninstalled" => "markAliasUninstalled", + "uninstall" => "remove", + "install" => "install", + other => panic!("Unexpected operation: {}", other), + }; + let package = operation.get_package(); + result_readable.push((job.to_string(), package.get_unique_name())); + result_ids.push((job.to_string(), vec![package.ptr_id()])); + } + } + + let mut expected_readable: Vec<(String, String)> = Vec::new(); + let mut expected_ids: Vec<(String, Vec<usize>)> = Vec::new(); + for job in &expected { + match job { + ExpectedJob::Single { job, package } => { + expected_readable.push((job.to_string(), package.get_unique_name())); + expected_ids.push((job.to_string(), vec![package.ptr_id()])); + } + ExpectedJob::Update { from, to } => { + expected_readable.push(( + "update".to_string(), + format!("{} => {}", from.get_unique_name(), to.get_unique_name()), + )); + expected_ids.push(("update".to_string(), vec![from.ptr_id(), to.ptr_id()])); + } + } + } + + assert_eq!(expected_readable, result_readable); + assert_eq!(expected_ids, result_ids); +} + +#[ignore] #[test] -#[ignore = "not yet ported (runs the Solver; constraint parsing uses a look-around regex)"] fn test_solver_install_single() { - let _fixtures = set_up(); - todo!() + let fixtures = set_up(); + let package_a = get_package("A", "1.0"); + fixtures.repo.add_package(package_a.clone()).unwrap(); + + let mut request = fixtures.request; + request.require_name("A", None).unwrap(); + + check_solver_result( + fixtures.repo_set, + fixtures.repo, + fixtures.repo_locked, + request, + vec![ExpectedJob::Single { + job: "install", + package: package_a, + }], + ); } +#[ignore] #[test] -#[ignore = "not yet ported (runs the Solver; constraint parsing uses a look-around regex)"] fn test_solver_remove_if_not_requested() { - let _fixtures = set_up(); - todo!() + let fixtures = set_up(); + let package_a = get_package("A", "1.0"); + fixtures.repo_locked.add_package(package_a.clone()).unwrap(); + + check_solver_result( + fixtures.repo_set, + fixtures.repo, + fixtures.repo_locked, + fixtures.request, + vec![ExpectedJob::Single { + job: "remove", + package: package_a, + }], + ); } +#[ignore = "solve() error path discards SolverProblemsException (returns placeholder anyhow error); getProblems/getCode/getPrettyString not retrievable"] #[test] -#[ignore = "not yet ported (runs the Solver; constraint parsing uses a look-around regex)"] fn test_install_non_existing_package_fails() { let _fixtures = set_up(); todo!() } +#[ignore] #[test] -#[ignore = "not yet ported (runs the Solver; constraint parsing uses a look-around regex)"] fn test_solver_install_same_package_from_different_repositories() { - let _fixtures = set_up(); - todo!() + let fixtures = set_up(); + let mut repo_set = fixtures.repo_set; + + let repo1 = ArrayRepository::new(vec![]).unwrap(); + let repo2 = ArrayRepository::new(vec![]).unwrap(); + + let foo1 = get_package("foo", "1"); + let foo2 = get_package("foo", "1"); + repo1.add_package(foo1.clone()).unwrap(); + repo2.add_package(foo2.clone()).unwrap(); + + repo_set + .add_repository(RepositoryInterfaceHandle::new(repo1)) + .unwrap(); + repo_set + .add_repository(RepositoryInterfaceHandle::new(repo2)) + .unwrap(); + + let mut request = fixtures.request; + request.require_name("foo", None).unwrap(); + + // The two repos are already added here; the helper adds the (empty) default repos too. + check_solver_result( + repo_set, + fixtures.repo, + fixtures.repo_locked, + request, + vec![ExpectedJob::Single { + job: "install", + package: foo1, + }], + ); } +#[ignore = "setRequires not available on CompletePackageHandle (only RootPackageHandle exposes link setters)"] #[test] -#[ignore = "not yet ported (runs the Solver; constraint parsing uses a look-around regex)"] fn test_solver_install_with_deps() { let _fixtures = set_up(); todo!() } +#[ignore = "setRequires not available on CompletePackageHandle (only RootPackageHandle exposes link setters)"] #[test] -#[ignore = "not yet ported (runs the Solver; constraint parsing uses a look-around regex)"] fn test_solver_install_honours_not_equal_operator() { let _fixtures = set_up(); todo!() } +#[ignore = "setRequires not available on CompletePackageHandle (only RootPackageHandle exposes link setters)"] #[test] -#[ignore = "not yet ported (runs the Solver; constraint parsing uses a look-around regex)"] fn test_solver_install_with_deps_in_order() { let _fixtures = set_up(); todo!() } +#[ignore = "setRequires not available on CompletePackageHandle (only RootPackageHandle exposes link setters)"] #[test] -#[ignore = "not yet ported (runs the Solver; constraint parsing uses a look-around regex)"] fn test_solver_multi_package_name_version_resolution_depends_on_require_order() { let _fixtures = set_up(); todo!() } +#[ignore = "setRequires not available on CompletePackageHandle (only RootPackageHandle exposes link setters)"] #[test] -#[ignore = "not yet ported (runs the Solver; constraint parsing uses a look-around regex)"] fn test_solver_multi_package_name_version_resolution_is_independent_of_require_order_if_ordered_descending_by_requirement() { let _fixtures = set_up(); todo!() } +#[ignore] #[test] -#[ignore = "not yet ported (runs the Solver; constraint parsing uses a look-around regex)"] fn test_solver_fix_locked() { - let _fixtures = set_up(); - todo!() + let fixtures = set_up(); + let package_a = get_package("A", "1.0"); + fixtures.repo_locked.add_package(package_a.clone()).unwrap(); + + let mut request = fixtures.request; + request.fix_package(package_a.clone()); + + check_solver_result( + fixtures.repo_set, + fixtures.repo, + fixtures.repo_locked, + request, + vec![], + ); } +#[ignore] #[test] -#[ignore = "not yet ported (runs the Solver; constraint parsing uses a look-around regex)"] fn test_solver_fix_locked_with_alternative() { - let _fixtures = set_up(); - todo!() + let fixtures = set_up(); + fixtures.repo.add_package(get_package("A", "1.0")).unwrap(); + let package_a = get_package("A", "1.0"); + fixtures.repo_locked.add_package(package_a.clone()).unwrap(); + + let mut request = fixtures.request; + request.fix_package(package_a.clone()); + + check_solver_result( + fixtures.repo_set, + fixtures.repo, + fixtures.repo_locked, + request, + vec![], + ); } +#[ignore = "setRequires not available on CompletePackageHandle (only RootPackageHandle exposes link setters)"] #[test] -#[ignore = "not yet ported (runs the Solver; constraint parsing uses a look-around regex)"] fn test_solver_update_does_only_update() { let _fixtures = set_up(); todo!() } +#[ignore] #[test] -#[ignore = "not yet ported (runs the Solver; constraint parsing uses a look-around regex)"] fn test_solver_update_single() { - let _fixtures = set_up(); - todo!() + let fixtures = set_up(); + let package_a = get_package("A", "1.0"); + fixtures.repo_locked.add_package(package_a.clone()).unwrap(); + let new_package_a = get_package("A", "1.1"); + fixtures.repo.add_package(new_package_a.clone()).unwrap(); + + let mut request = fixtures.request; + request.require_name("A", None).unwrap(); + + check_solver_result( + fixtures.repo_set, + fixtures.repo, + fixtures.repo_locked, + request, + vec![ExpectedJob::Update { + from: package_a, + to: new_package_a, + }], + ); } +#[ignore = "setRequires not available on CompletePackageHandle (only RootPackageHandle exposes link setters)"] #[test] -#[ignore = "not yet ported (runs the Solver; constraint parsing uses a look-around regex)"] fn test_solver_update_all() { let _fixtures = set_up(); todo!() } +#[ignore] #[test] -#[ignore = "not yet ported (runs the Solver; constraint parsing uses a look-around regex)"] fn test_solver_update_current() { - let _fixtures = set_up(); - todo!() + let fixtures = set_up(); + fixtures + .repo_locked + .add_package(get_package("A", "1.0")) + .unwrap(); + fixtures.repo.add_package(get_package("A", "1.0")).unwrap(); + + let mut request = fixtures.request; + request.require_name("A", None).unwrap(); + + check_solver_result( + fixtures.repo_set, + fixtures.repo, + fixtures.repo_locked, + request, + vec![], + ); } +#[ignore] #[test] -#[ignore = "not yet ported (runs the Solver; constraint parsing uses a look-around regex)"] fn test_solver_update_only_updates_selected_package() { - let _fixtures = set_up(); - todo!() + let fixtures = set_up(); + let package_a = get_package("A", "1.0"); + fixtures.repo_locked.add_package(package_a.clone()).unwrap(); + let package_b = get_package("B", "1.0"); + fixtures.repo_locked.add_package(package_b.clone()).unwrap(); + let package_a_newer = get_package("A", "1.1"); + fixtures.repo.add_package(package_a_newer.clone()).unwrap(); + let package_b_newer = get_package("B", "1.1"); + fixtures.repo.add_package(package_b_newer.clone()).unwrap(); + + let mut request = fixtures.request; + request.require_name("A", None).unwrap(); + request.fix_package(package_b.clone()); + + check_solver_result( + fixtures.repo_set, + fixtures.repo, + fixtures.repo_locked, + request, + vec![ExpectedJob::Update { + from: package_a, + to: package_a_newer, + }], + ); } +#[ignore] #[test] -#[ignore = "not yet ported (runs the Solver; constraint parsing uses a look-around regex)"] fn test_solver_update_constrained() { - let _fixtures = set_up(); - todo!() + let fixtures = set_up(); + let package_a = get_package("A", "1.0"); + fixtures.repo_locked.add_package(package_a.clone()).unwrap(); + let new_package_a = get_package("A", "1.2"); + fixtures.repo.add_package(new_package_a.clone()).unwrap(); + fixtures.repo.add_package(get_package("A", "2.0")).unwrap(); + + let mut request = fixtures.request; + request + .require_name("A", Some(get_version_constraint("<", "2.0.0.0"))) + .unwrap(); + + check_solver_result( + fixtures.repo_set, + fixtures.repo, + fixtures.repo_locked, + request, + vec![ExpectedJob::Update { + from: package_a, + to: new_package_a, + }], + ); } +#[ignore] #[test] -#[ignore = "not yet ported (runs the Solver; constraint parsing uses a look-around regex)"] fn test_solver_update_fully_constrained() { - let _fixtures = set_up(); - todo!() + let fixtures = set_up(); + let package_a = get_package("A", "1.0"); + fixtures.repo_locked.add_package(package_a.clone()).unwrap(); + let new_package_a = get_package("A", "1.2"); + fixtures.repo.add_package(new_package_a.clone()).unwrap(); + fixtures.repo.add_package(get_package("A", "2.0")).unwrap(); + + let mut request = fixtures.request; + request + .require_name("A", Some(get_version_constraint("<", "2.0.0.0"))) + .unwrap(); + + check_solver_result( + fixtures.repo_set, + fixtures.repo, + fixtures.repo_locked, + request, + vec![ExpectedJob::Update { + from: package_a, + to: new_package_a, + }], + ); } +#[ignore] #[test] -#[ignore = "not yet ported (runs the Solver; constraint parsing uses a look-around regex)"] fn test_solver_update_fully_constrained_prunes_installed_packages() { - let _fixtures = set_up(); - todo!() + let fixtures = set_up(); + let package_a = get_package("A", "1.0"); + fixtures.repo_locked.add_package(package_a.clone()).unwrap(); + let package_b = get_package("B", "1.0"); + fixtures.repo_locked.add_package(package_b.clone()).unwrap(); + let new_package_a = get_package("A", "1.2"); + fixtures.repo.add_package(new_package_a.clone()).unwrap(); + fixtures.repo.add_package(get_package("A", "2.0")).unwrap(); + + let mut request = fixtures.request; + request + .require_name("A", Some(get_version_constraint("<", "2.0.0.0"))) + .unwrap(); + + check_solver_result( + fixtures.repo_set, + fixtures.repo, + fixtures.repo_locked, + request, + vec![ + ExpectedJob::Single { + job: "remove", + package: package_b, + }, + ExpectedJob::Update { + from: package_a, + to: new_package_a, + }, + ], + ); } +#[ignore = "setRequires not available on CompletePackageHandle (only RootPackageHandle exposes link setters)"] #[test] -#[ignore = "not yet ported (runs the Solver; constraint parsing uses a look-around regex)"] fn test_solver_all_jobs() { let _fixtures = set_up(); todo!() } +#[ignore = "setRequires/setConflicts not available on CompletePackageHandle (only RootPackageHandle exposes link setters)"] #[test] -#[ignore = "not yet ported (runs the Solver; constraint parsing uses a look-around regex)"] fn test_solver_three_alternative_require_and_conflict() { let _fixtures = set_up(); todo!() } +#[ignore = "setReplaces not available on CompletePackageHandle (only RootPackageHandle exposes link setters)"] #[test] -#[ignore = "not yet ported (runs the Solver; constraint parsing uses a look-around regex)"] fn test_solver_obsolete() { let _fixtures = set_up(); todo!() } +#[ignore] #[test] -#[ignore = "not yet ported (runs the Solver; constraint parsing uses a look-around regex)"] fn test_install_one_of_two_alternatives() { - let _fixtures = set_up(); - todo!() + let fixtures = set_up(); + let package_a = get_package("A", "1.0"); + fixtures.repo.add_package(package_a.clone()).unwrap(); + let package_b = get_package("A", "1.0"); + fixtures.repo.add_package(package_b.clone()).unwrap(); + + let mut request = fixtures.request; + request.require_name("A", None).unwrap(); + + check_solver_result( + fixtures.repo_set, + fixtures.repo, + fixtures.repo_locked, + request, + vec![ExpectedJob::Single { + job: "install", + package: package_a, + }], + ); } +#[ignore = "setRequires/setProvides not available on CompletePackageHandle (only RootPackageHandle exposes link setters)"] #[test] -#[ignore = "not yet ported (runs the Solver; constraint parsing uses a look-around regex)"] fn test_install_provider() { let _fixtures = set_up(); todo!() } +#[ignore = "setRequires/setReplaces not available on CompletePackageHandle (only RootPackageHandle exposes link setters)"] #[test] -#[ignore = "not yet ported (runs the Solver; constraint parsing uses a look-around regex)"] fn test_skip_replacer_of_existing_package() { let _fixtures = set_up(); todo!() } +#[ignore = "setRequires/setReplaces not available on CompletePackageHandle (only RootPackageHandle exposes link setters)"] #[test] -#[ignore = "not yet ported (runs the Solver; constraint parsing uses a look-around regex)"] fn test_no_install_replacer_of_missing_package() { let _fixtures = set_up(); todo!() } +#[ignore = "setRequires/setReplaces not available on CompletePackageHandle (only RootPackageHandle exposes link setters)"] #[test] -#[ignore = "not yet ported (runs the Solver; constraint parsing uses a look-around regex)"] fn test_skip_replaced_package_if_replacer_is_selected() { let _fixtures = set_up(); todo!() } +#[ignore = "setRequires/setReplaces not available on CompletePackageHandle (only RootPackageHandle exposes link setters)"] #[test] -#[ignore = "not yet ported (runs the Solver; constraint parsing uses a look-around regex)"] fn test_pick_older_if_newer_conflicts() { let _fixtures = set_up(); todo!() } +#[ignore = "setRequires not available on CompletePackageHandle (only RootPackageHandle exposes link setters)"] #[test] -#[ignore = "not yet ported (runs the Solver; constraint parsing uses a look-around regex)"] fn test_install_circular_require() { let _fixtures = set_up(); todo!() } +#[ignore = "setRequires/setProvides not available on CompletePackageHandle (only RootPackageHandle exposes link setters)"] #[test] -#[ignore = "not yet ported (runs the Solver; constraint parsing uses a look-around regex)"] fn test_install_alternative_with_circular_require() { let _fixtures = set_up(); todo!() } +#[ignore = "setRequires/setReplaces not available on CompletePackageHandle (only RootPackageHandle exposes link setters)"] #[test] -#[ignore = "not yet ported (runs the Solver; constraint parsing uses a look-around regex)"] fn test_use_replacer_if_necessary() { let _fixtures = set_up(); todo!() } +#[ignore = "setRequires/setReplaces not available on CompletePackageHandle (only RootPackageHandle exposes link setters)"] #[test] -#[ignore = "not yet ported (runs the Solver; constraint parsing uses a look-around regex)"] fn test_issue265() { let _fixtures = set_up(); todo!() } +#[ignore = "setConflicts not available on CompletePackageHandle (only RootPackageHandle exposes link setters)"] #[test] -#[ignore = "not yet ported (runs the Solver; constraint parsing uses a look-around regex)"] fn test_conflict_result_empty() { let _fixtures = set_up(); todo!() } +#[ignore = "setRequires not available on CompletePackageHandle; also asserts SolverProblemsException details which solve() discards"] #[test] -#[ignore = "not yet ported (runs the Solver; constraint parsing uses a look-around regex)"] fn test_unsatisfiable_requires() { let _fixtures = set_up(); todo!() } +#[ignore = "setRequires not available on CompletePackageHandle; also asserts SolverProblemsException details which solve() discards"] #[test] -#[ignore = "not yet ported (runs the Solver; constraint parsing uses a look-around regex)"] fn test_require_mismatch_exception() { let _fixtures = set_up(); todo!() } +#[ignore = "setRequires/setReplaces not available on CompletePackageHandle (only RootPackageHandle exposes link setters)"] #[test] -#[ignore = "not yet ported (runs the Solver; constraint parsing uses a look-around regex)"] fn test_learn_literals_with_sorted_rule_literals() { let _fixtures = set_up(); todo!() } +#[ignore = "setRequires not available on CompletePackageHandle (only RootPackageHandle exposes link setters)"] #[test] -#[ignore = "not yet ported (runs the Solver; constraint parsing uses a look-around regex)"] fn test_install_recursive_alias_dependencies() { let _fixtures = set_up(); todo!() } +#[ignore = "setRequires not available on CompletePackageHandle (only RootPackageHandle exposes link setters)"] #[test] -#[ignore = "not yet ported (runs the Solver; constraint parsing uses a look-around regex)"] fn test_install_dev_alias() { let _fixtures = set_up(); todo!() } +#[ignore] #[test] -#[ignore = "not yet ported (runs the Solver; constraint parsing uses a look-around regex)"] fn test_install_root_aliases_if_alias_of_is_installed() { - let _fixtures = set_up(); - todo!() + let fixtures = set_up(); + + // root aliased, required + let package_a = get_package("A", "1.0"); + fixtures.repo.add_package(package_a.clone()).unwrap(); + let package_a_alias = get_alias_package(&package_a, "1.1"); + fixtures.repo.add_package(package_a_alias.clone()).unwrap(); + package_a_alias + .as_alias() + .unwrap() + .set_root_package_alias(true); + // root aliased, not required, should still be installed as it is root alias + let package_b = get_package("B", "1.0"); + fixtures.repo.add_package(package_b.clone()).unwrap(); + let package_b_alias = get_alias_package(&package_b, "1.1"); + fixtures.repo.add_package(package_b_alias.clone()).unwrap(); + package_b_alias + .as_alias() + .unwrap() + .set_root_package_alias(true); + // regular alias, not required, alias should not be installed + let package_c = get_package("C", "1.0"); + fixtures.repo.add_package(package_c.clone()).unwrap(); + let package_c_alias = get_alias_package(&package_c, "1.1"); + fixtures.repo.add_package(package_c_alias.clone()).unwrap(); + + let mut request = fixtures.request; + request + .require_name("A", Some(get_version_constraint("==", "1.1"))) + .unwrap(); + request + .require_name("B", Some(get_version_constraint("==", "1.0"))) + .unwrap(); + request + .require_name("C", Some(get_version_constraint("==", "1.0"))) + .unwrap(); + + check_solver_result( + fixtures.repo_set, + fixtures.repo, + fixtures.repo_locked, + request, + vec![ + ExpectedJob::Single { + job: "install", + package: package_a, + }, + ExpectedJob::Single { + job: "markAliasInstalled", + package: package_a_alias, + }, + ExpectedJob::Single { + job: "install", + package: package_b, + }, + ExpectedJob::Single { + job: "markAliasInstalled", + package: package_b_alias, + }, + ExpectedJob::Single { + job: "install", + package: package_c, + }, + ExpectedJob::Single { + job: "markAliasInstalled", + package: package_c_alias, + }, + ], + ); } +#[ignore = "setRequires not available on CompletePackageHandle (only RootPackageHandle exposes link setters)"] #[test] -#[ignore = "not yet ported (runs the Solver; constraint parsing uses a look-around regex)"] fn test_learn_positive_literal() { let _fixtures = set_up(); todo!() diff --git a/crates/shirabe/tests/dependency_resolver/transaction_test.rs b/crates/shirabe/tests/dependency_resolver/transaction_test.rs index 2ea74aa..a5ca310 100644 --- a/crates/shirabe/tests/dependency_resolver/transaction_test.rs +++ b/crates/shirabe/tests/dependency_resolver/transaction_test.rs @@ -1,10 +1,7 @@ //! ref: composer/tests/Composer/Test/DependencyResolver/TransactionTest.php -// Transaction::new sorts operations via shirabe_php_shim::uasort_map, which is todo!(). -// The fixture also calls setRequires/setProvides on non-root packages, which the public -// handle API does not allow, so the scenario cannot be expressed faithfully yet. #[test] -#[ignore = "Transaction::new reaches uasort_map (todo!()); fixture needs link setters on non-root packages"] +#[ignore = "CompletePackageHandle lacks set_type/set_requires/set_provides/set_extra; these setters exist only on RootPackageHandle, so the non-root package fixture cannot be expressed"] fn test_transaction_generation_and_sorting() { todo!() } diff --git a/crates/shirabe/tests/documentation_test.rs b/crates/shirabe/tests/documentation_test.rs index 9ea9d37..7c9bef1 100644 --- a/crates/shirabe/tests/documentation_test.rs +++ b/crates/shirabe/tests/documentation_test.rs @@ -1,7 +1,7 @@ //! ref: composer/tests/Composer/Test/DocumentationTest.php #[test] -#[ignore = "requires the console Application and Symfony ApplicationDescription to enumerate commands, which are not yet ported"] +#[ignore = "Application::set_auto_exit (setAutoExit) does not exist, required by provideCommandCases"] fn test_command() { todo!() } diff --git a/crates/shirabe/tests/downloader/archive_downloader_test.rs b/crates/shirabe/tests/downloader/archive_downloader_test.rs index 2c0e2de..dfba3bf 100644 --- a/crates/shirabe/tests/downloader/archive_downloader_test.rs +++ b/crates/shirabe/tests/downloader/archive_downloader_test.rs @@ -5,37 +5,37 @@ // packages. #[test] -#[ignore = "constructs an ArchiveDownloader (HttpDownloader reaches curl_multi_init, todo!()) with a mocked subclass/package"] +#[ignore = "FileDownloader::get_file_name is pub(crate); not reachable from the integration test crate"] fn test_get_file_name() { todo!() } #[test] -#[ignore = "constructs an ArchiveDownloader (HttpDownloader reaches curl_multi_init, todo!()) with a mocked subclass/package"] +#[ignore = "FileDownloader::process_url is pub(crate); not reachable from the integration test crate"] fn test_process_url() { todo!() } #[test] -#[ignore = "constructs an ArchiveDownloader (HttpDownloader reaches curl_multi_init, todo!()) with a mocked subclass/package"] +#[ignore = "FileDownloader::process_url is pub(crate); not reachable from the integration test crate"] fn test_process_url2() { todo!() } #[test] -#[ignore = "constructs an ArchiveDownloader (HttpDownloader reaches curl_multi_init, todo!()) with a mocked subclass/package"] +#[ignore = "FileDownloader::process_url is pub(crate); not reachable from the integration test crate"] fn test_process_url3() { todo!() } #[test] -#[ignore = "constructs an ArchiveDownloader (HttpDownloader reaches curl_multi_init, todo!()) with a mocked subclass/package"] +#[ignore = "FileDownloader::process_url is pub(crate); not reachable from the integration test crate"] fn test_process_url_rewrite_dist() { todo!() } #[test] -#[ignore = "constructs an ArchiveDownloader (HttpDownloader reaches curl_multi_init, todo!()) with a mocked subclass/package"] +#[ignore = "FileDownloader::process_url is pub(crate); not reachable from the integration test crate"] fn test_process_url_rewrite_bitbucket_dist() { todo!() } diff --git a/crates/shirabe/tests/downloader/download_manager_test.rs b/crates/shirabe/tests/downloader/download_manager_test.rs index f9c44f4..b7fa976 100644 --- a/crates/shirabe/tests/downloader/download_manager_test.rs +++ b/crates/shirabe/tests/downloader/download_manager_test.rs @@ -7,218 +7,218 @@ fn set_up() { // These mock IO and individual downloaders to drive DownloadManager's selection/download/ // update/remove logic; mocking is not available here. +#[ignore = "requires PHPUnit mock of DownloaderInterface (createDownloaderMock)"] #[test] -#[ignore = "mocks IO and individual downloaders to drive DownloadManager; mocking is not available"] fn test_set_get_downloader() { set_up(); todo!() } +#[ignore = "requires PHPUnit mock of PackageInterface (createPackageMock)"] #[test] -#[ignore = "mocks IO and individual downloaders to drive DownloadManager; mocking is not available"] fn test_get_downloader_for_incorrectly_installed_package() { set_up(); todo!() } +#[ignore = "requires PHPUnit mocks and partial mock of DownloadManager::getDownloader"] #[test] -#[ignore = "mocks IO and individual downloaders to drive DownloadManager; mocking is not available"] fn test_get_downloader_for_correctly_installed_dist_package() { set_up(); todo!() } +#[ignore = "requires PHPUnit mocks and partial mock of DownloadManager::getDownloader"] #[test] -#[ignore = "mocks IO and individual downloaders to drive DownloadManager; mocking is not available"] fn test_get_downloader_for_incorrectly_installed_dist_package() { set_up(); todo!() } +#[ignore = "requires PHPUnit mocks and partial mock of DownloadManager::getDownloader"] #[test] -#[ignore = "mocks IO and individual downloaders to drive DownloadManager; mocking is not available"] fn test_get_downloader_for_correctly_installed_source_package() { set_up(); todo!() } +#[ignore = "requires PHPUnit mocks and partial mock of DownloadManager::getDownloader"] #[test] -#[ignore = "mocks IO and individual downloaders to drive DownloadManager; mocking is not available"] fn test_get_downloader_for_incorrectly_installed_source_package() { set_up(); todo!() } +#[ignore = "requires PHPUnit mock of PackageInterface (createPackageMock)"] #[test] -#[ignore = "mocks IO and individual downloaders to drive DownloadManager; mocking is not available"] fn test_get_downloader_for_metapackage() { set_up(); todo!() } +#[ignore = "requires PHPUnit mocks and partial mock of DownloadManager::getDownloaderForPackage"] #[test] -#[ignore = "mocks IO and individual downloaders to drive DownloadManager; mocking is not available"] fn test_full_package_download() { set_up(); todo!() } +#[ignore = "requires PHPUnit mocks and partial mock of DownloadManager::getDownloaderForPackage"] #[test] -#[ignore = "mocks IO and individual downloaders to drive DownloadManager; mocking is not available"] fn test_full_package_download_failover() { set_up(); todo!() } +#[ignore = "requires PHPUnit mock of PackageInterface (createPackageMock)"] #[test] -#[ignore = "mocks IO and individual downloaders to drive DownloadManager; mocking is not available"] fn test_bad_package_download() { set_up(); todo!() } +#[ignore = "requires PHPUnit mocks and partial mock of DownloadManager::getDownloaderForPackage"] #[test] -#[ignore = "mocks IO and individual downloaders to drive DownloadManager; mocking is not available"] fn test_dist_only_package_download() { set_up(); todo!() } +#[ignore = "requires PHPUnit mocks and partial mock of DownloadManager::getDownloaderForPackage"] #[test] -#[ignore = "mocks IO and individual downloaders to drive DownloadManager; mocking is not available"] fn test_source_only_package_download() { set_up(); todo!() } +#[ignore = "requires PHPUnit mocks and partial mock of DownloadManager::getDownloaderForPackage"] #[test] -#[ignore = "mocks IO and individual downloaders to drive DownloadManager; mocking is not available"] fn test_metapackage_package_download() { set_up(); todo!() } +#[ignore = "requires PHPUnit mocks and partial mock of DownloadManager::getDownloaderForPackage"] #[test] -#[ignore = "mocks IO and individual downloaders to drive DownloadManager; mocking is not available"] fn test_full_package_download_with_source_preferred() { set_up(); todo!() } +#[ignore = "requires PHPUnit mocks and partial mock of DownloadManager::getDownloaderForPackage"] #[test] -#[ignore = "mocks IO and individual downloaders to drive DownloadManager; mocking is not available"] fn test_dist_only_package_download_with_source_preferred() { set_up(); todo!() } +#[ignore = "requires PHPUnit mocks and partial mock of DownloadManager::getDownloaderForPackage"] #[test] -#[ignore = "mocks IO and individual downloaders to drive DownloadManager; mocking is not available"] fn test_source_only_package_download_with_source_preferred() { set_up(); todo!() } +#[ignore = "requires PHPUnit mock of PackageInterface (createPackageMock)"] #[test] -#[ignore = "mocks IO and individual downloaders to drive DownloadManager; mocking is not available"] fn test_bad_package_download_with_source_preferred() { set_up(); todo!() } +#[ignore = "requires PHPUnit mocks of PackageInterface and DownloaderInterface"] #[test] -#[ignore = "mocks IO and individual downloaders to drive DownloadManager; mocking is not available"] fn test_update_dist_with_equal_types() { set_up(); todo!() } +#[ignore = "requires PHPUnit mocks of PackageInterface and DownloaderInterface"] #[test] -#[ignore = "mocks IO and individual downloaders to drive DownloadManager; mocking is not available"] fn test_update_dist_with_not_equal_types() { set_up(); todo!() } +#[ignore = "requires PHPUnit mock of PackageInterface and ReflectionMethod for private getAvailableSources"] #[test] -#[ignore = "mocks IO and individual downloaders to drive DownloadManager; mocking is not available"] fn test_get_available_sources_update_sticks_to_same_source() { set_up(); todo!() } +#[ignore = "requires PHPUnit mocks and partial mock of DownloadManager::getDownloaderForPackage"] #[test] -#[ignore = "mocks IO and individual downloaders to drive DownloadManager; mocking is not available"] fn test_update_metapackage() { set_up(); todo!() } +#[ignore = "requires PHPUnit mocks and partial mock of DownloadManager::getDownloaderForPackage"] #[test] -#[ignore = "mocks IO and individual downloaders to drive DownloadManager; mocking is not available"] fn test_remove() { set_up(); todo!() } +#[ignore = "requires PHPUnit mocks and partial mock of DownloadManager::getDownloaderForPackage"] #[test] -#[ignore = "mocks IO and individual downloaders to drive DownloadManager; mocking is not available"] fn test_metapackage_remove() { set_up(); todo!() } +#[ignore = "requires PHPUnit mocks and partial mock of DownloadManager::getDownloaderForPackage"] #[test] -#[ignore = "mocks IO and individual downloaders to drive DownloadManager; mocking is not available"] fn test_install_preference_without_preference_dev() { set_up(); todo!() } +#[ignore = "requires PHPUnit mocks and partial mock of DownloadManager::getDownloaderForPackage"] #[test] -#[ignore = "mocks IO and individual downloaders to drive DownloadManager; mocking is not available"] fn test_install_preference_without_preference_no_dev() { set_up(); todo!() } +#[ignore = "requires PHPUnit mocks and partial mock of DownloadManager::getDownloaderForPackage"] #[test] -#[ignore = "mocks IO and individual downloaders to drive DownloadManager; mocking is not available"] fn test_install_preference_without_match_dev() { set_up(); todo!() } +#[ignore = "requires PHPUnit mocks and partial mock of DownloadManager::getDownloaderForPackage"] #[test] -#[ignore = "mocks IO and individual downloaders to drive DownloadManager; mocking is not available"] fn test_install_preference_without_match_no_dev() { set_up(); todo!() } +#[ignore = "requires PHPUnit mocks and partial mock of DownloadManager::getDownloaderForPackage"] #[test] -#[ignore = "mocks IO and individual downloaders to drive DownloadManager; mocking is not available"] fn test_install_preference_with_match_auto_dev() { set_up(); todo!() } +#[ignore = "requires PHPUnit mocks and partial mock of DownloadManager::getDownloaderForPackage"] #[test] -#[ignore = "mocks IO and individual downloaders to drive DownloadManager; mocking is not available"] fn test_install_preference_with_match_auto_no_dev() { set_up(); todo!() } +#[ignore = "requires PHPUnit mocks and partial mock of DownloadManager::getDownloaderForPackage"] #[test] -#[ignore = "mocks IO and individual downloaders to drive DownloadManager; mocking is not available"] fn test_install_preference_with_match_source() { set_up(); todo!() } +#[ignore = "requires PHPUnit mocks and partial mock of DownloadManager::getDownloaderForPackage"] #[test] -#[ignore = "mocks IO and individual downloaders to drive DownloadManager; mocking is not available"] fn test_install_preference_with_match_dist() { set_up(); todo!() diff --git a/crates/shirabe/tests/downloader/file_downloader_test.rs b/crates/shirabe/tests/downloader/file_downloader_test.rs index 9e376be..b99dd93 100644 --- a/crates/shirabe/tests/downloader/file_downloader_test.rs +++ b/crates/shirabe/tests/downloader/file_downloader_test.rs @@ -8,63 +8,63 @@ fn set_up() { // These construct a FileDownloader with a mocked IO/HttpDownloader (curl_multi_init todo!()) // and a mocked Cache/Package to drive download/checksum behaviour. #[test] -#[ignore = "mocks IO/HttpDownloader (curl_multi_init todo!()) and Cache/Package"] +#[ignore = "requires PHPUnit mock of HttpDownloader::addCopy (getDownloader helper); no mocking framework and real HttpDownloader reaches curl_multi_init todo!()"] fn test_download_for_package_without_dist_reference() { set_up(); todo!() } #[test] -#[ignore = "mocks IO/HttpDownloader (curl_multi_init todo!()) and Cache/Package"] +#[ignore = "requires PHPUnit mock of HttpDownloader::addCopy (getDownloader helper); no mocking framework and real HttpDownloader reaches curl_multi_init todo!()"] fn test_download_to_existing_file() { set_up(); todo!() } #[test] -#[ignore = "mocks IO/HttpDownloader (curl_multi_init todo!()) and Cache/Package"] +#[ignore = "requires PHPUnit mock of HttpDownloader::addCopy (getDownloader helper) and ReflectionMethod on private getFileName; no mocking/reflection framework"] fn test_get_file_name() { set_up(); todo!() } #[test] -#[ignore = "mocks IO/HttpDownloader (curl_multi_init todo!()) and Cache/Package"] +#[ignore = "requires PHPUnit mock of HttpDownloader::addCopy and IOInterface::write callback; no mocking framework and real HttpDownloader reaches curl_multi_init todo!()"] fn test_download_but_file_is_unsaved() { set_up(); todo!() } #[test] -#[ignore = "mocks IO/HttpDownloader (curl_multi_init todo!()) and Cache/Package"] +#[ignore = "requires PHPUnit mocks of Cache::copyTo/copyFrom and HttpDownloader::addCopy with assertion callbacks; no mocking framework"] fn test_download_with_custom_processed_url() { set_up(); todo!() } #[test] -#[ignore = "mocks IO/HttpDownloader (curl_multi_init todo!()) and Cache/Package"] +#[ignore = "requires PHPUnit mocks of Cache::copyTo/copyFrom and HttpDownloader::addCopy with assertion callbacks; no mocking framework"] fn test_download_with_custom_cache_key() { set_up(); todo!() } #[test] -#[ignore = "mocks IO/HttpDownloader (curl_multi_init todo!()) and Cache/Package"] +#[ignore = "requires PHPUnit mock of Cache::gcIsNecessary/gc with expectation tracking; no mocking framework"] fn test_cache_garbage_collection_is_called() { set_up(); todo!() } #[test] -#[ignore = "mocks IO/HttpDownloader (curl_multi_init todo!()) and Cache/Package"] +#[ignore = "requires PHPUnit mock of Filesystem and HttpDownloader::addCopy plus ReflectionMethod on private getFileName; no mocking/reflection framework"] fn test_download_file_with_invalid_checksum() { set_up(); todo!() } #[test] -#[ignore = "mocks IO/HttpDownloader (curl_multi_init todo!()) and Cache/Package"] +#[ignore = "requires PHPUnit mocks of Filesystem::removeDirectoryAsync/normalizePath, HttpDownloader::addCopy, getIOMock expectations and ReflectionMethod on private getFileName; no mocking/reflection framework"] fn test_downgrade_shows_appropriate_message() { set_up(); todo!() diff --git a/crates/shirabe/tests/downloader/fossil_downloader_test.rs b/crates/shirabe/tests/downloader/fossil_downloader_test.rs index 9c9dbe4..0c45d2c 100644 --- a/crates/shirabe/tests/downloader/fossil_downloader_test.rs +++ b/crates/shirabe/tests/downloader/fossil_downloader_test.rs @@ -35,7 +35,7 @@ impl Drop for TearDown { // curl_multi_init (todo!()), and ProcessExecutor mocking is not available. #[test] -#[ignore = "mocks ProcessExecutor/IO and needs an HttpDownloader (curl_multi_init todo!())"] +#[ignore = "needs ProcessExecutorMock and PHPUnit mocks of IOInterface/PackageInterface/Filesystem for getDownloaderMock; not available"] fn test_install_for_package_without_source_reference() { let working_dir = set_up(); let _tear_down = TearDown::new(working_dir.path().to_path_buf()); @@ -44,7 +44,7 @@ fn test_install_for_package_without_source_reference() { } #[test] -#[ignore = "mocks ProcessExecutor/IO and needs an HttpDownloader (curl_multi_init todo!())"] +#[ignore = "needs ProcessExecutorMock with expects() command-sequence assertions and PHPUnit PackageInterface mock; not available"] fn test_install() { let working_dir = set_up(); let _tear_down = TearDown::new(working_dir.path().to_path_buf()); @@ -53,7 +53,7 @@ fn test_install() { } #[test] -#[ignore = "mocks ProcessExecutor/IO and needs an HttpDownloader (curl_multi_init todo!())"] +#[ignore = "needs ProcessExecutorMock and PHPUnit mocks of IOInterface/PackageInterface/Filesystem for getDownloaderMock; not available"] fn test_updatefor_package_without_source_reference() { let working_dir = set_up(); let _tear_down = TearDown::new(working_dir.path().to_path_buf()); @@ -62,7 +62,7 @@ fn test_updatefor_package_without_source_reference() { } #[test] -#[ignore = "mocks ProcessExecutor/IO and needs an HttpDownloader (curl_multi_init todo!())"] +#[ignore = "needs ProcessExecutorMock with expects() command-sequence assertions and PHPUnit PackageInterface mock; not available"] fn test_update() { let working_dir = set_up(); let _tear_down = TearDown::new(working_dir.path().to_path_buf()); @@ -71,7 +71,7 @@ fn test_update() { } #[test] -#[ignore = "mocks ProcessExecutor/IO and needs an HttpDownloader (curl_multi_init todo!())"] +#[ignore = "needs ProcessExecutorMock and a PHPUnit Filesystem mock asserting removeDirectoryAsync; not available"] fn test_remove() { let working_dir = set_up(); let _tear_down = TearDown::new(working_dir.path().to_path_buf()); @@ -80,7 +80,7 @@ fn test_remove() { } #[test] -#[ignore = "mocks ProcessExecutor/IO and needs an HttpDownloader (curl_multi_init todo!())"] +#[ignore = "needs ProcessExecutorMock and PHPUnit mocks of IOInterface/Filesystem for getDownloaderMock; not available"] fn test_get_installation_source() { let working_dir = set_up(); let _tear_down = TearDown::new(working_dir.path().to_path_buf()); diff --git a/crates/shirabe/tests/downloader/git_downloader_test.rs b/crates/shirabe/tests/downloader/git_downloader_test.rs index c820254..69b0a6c 100644 --- a/crates/shirabe/tests/downloader/git_downloader_test.rs +++ b/crates/shirabe/tests/downloader/git_downloader_test.rs @@ -44,8 +44,8 @@ impl Drop for TearDown { // These construct a GitDownloader with a mocked IO/Config and a mocked ProcessExecutor to // feed git command output; mocking is not available, and a real HttpDownloader reaches // curl_multi_init (todo!()). +#[ignore = "requires getProcessExecutorMock/getIOMock and getMockBuilder PackageInterface mock; no ProcessExecutorMock/IOMock mocking infrastructure exists"] #[test] -#[ignore = "mocks ProcessExecutor/IO and needs an HttpDownloader (curl_multi_init todo!())"] fn test_download_for_package_without_source_reference() { let working_dir = set_up(); let _tear_down = TearDown::new(working_dir.path().to_path_buf()); @@ -53,8 +53,8 @@ fn test_download_for_package_without_source_reference() { todo!() } +#[ignore = "requires getProcessExecutorMock with expects() command expectations and getMockBuilder PackageInterface mock; no mocking infrastructure exists"] #[test] -#[ignore = "mocks ProcessExecutor/IO and needs an HttpDownloader (curl_multi_init todo!())"] fn test_download() { let working_dir = set_up(); let _tear_down = TearDown::new(working_dir.path().to_path_buf()); @@ -62,8 +62,8 @@ fn test_download() { todo!() } +#[ignore = "requires getProcessExecutorMock with expects() command expectations and initGitVersion reflection on Git::version static cache; no mocking infrastructure exists"] #[test] -#[ignore = "mocks ProcessExecutor/IO and needs an HttpDownloader (curl_multi_init todo!())"] fn test_download_with_cache() { let working_dir = set_up(); let _tear_down = TearDown::new(working_dir.path().to_path_buf()); @@ -71,8 +71,8 @@ fn test_download_with_cache() { todo!() } +#[ignore = "requires getProcessExecutorMock with expects() command expectations and getMockBuilder PackageInterface mock; no mocking infrastructure exists"] #[test] -#[ignore = "mocks ProcessExecutor/IO and needs an HttpDownloader (curl_multi_init todo!())"] fn test_download_uses_various_protocols_and_sets_push_url_for_github() { let working_dir = set_up(); let _tear_down = TearDown::new(working_dir.path().to_path_buf()); @@ -80,8 +80,8 @@ fn test_download_uses_various_protocols_and_sets_push_url_for_github() { todo!() } +#[ignore = "requires getProcessExecutorMock with expects() command expectations and pushUrlProvider dataProvider; no mocking infrastructure exists"] #[test] -#[ignore = "mocks ProcessExecutor/IO and needs an HttpDownloader (curl_multi_init todo!())"] fn test_download_and_set_push_url_use_custom_various_protocols_for_github() { let working_dir = set_up(); let _tear_down = TearDown::new(working_dir.path().to_path_buf()); @@ -89,8 +89,8 @@ fn test_download_and_set_push_url_use_custom_various_protocols_for_github() { todo!() } +#[ignore = "requires getProcessExecutorMock with expects() command expectations and getMockBuilder PackageInterface mock; no mocking infrastructure exists"] #[test] -#[ignore = "mocks ProcessExecutor/IO and needs an HttpDownloader (curl_multi_init todo!())"] fn test_download_throws_runtime_exception_if_git_command_fails() { let working_dir = set_up(); let _tear_down = TearDown::new(working_dir.path().to_path_buf()); @@ -98,8 +98,8 @@ fn test_download_throws_runtime_exception_if_git_command_fails() { todo!() } +#[ignore = "requires getProcessExecutorMock/getDownloaderMock and getMockBuilder PackageInterface mock; no mocking infrastructure exists"] #[test] -#[ignore = "mocks ProcessExecutor/IO and needs an HttpDownloader (curl_multi_init todo!())"] fn test_updatefor_package_without_source_reference() { let working_dir = set_up(); let _tear_down = TearDown::new(working_dir.path().to_path_buf()); @@ -107,8 +107,8 @@ fn test_updatefor_package_without_source_reference() { todo!() } +#[ignore = "requires getProcessExecutorMock with expects() command expectations and getMockBuilder PackageInterface mock; no mocking infrastructure exists"] #[test] -#[ignore = "mocks ProcessExecutor/IO and needs an HttpDownloader (curl_multi_init todo!())"] fn test_update() { let working_dir = set_up(); let _tear_down = TearDown::new(working_dir.path().to_path_buf()); @@ -116,8 +116,8 @@ fn test_update() { todo!() } +#[ignore = "requires getProcessExecutorMock with expects() command expectations and getMockBuilder PackageInterface mock; no mocking infrastructure exists"] #[test] -#[ignore = "mocks ProcessExecutor/IO and needs an HttpDownloader (curl_multi_init todo!())"] fn test_update_with_new_repo_url() { let working_dir = set_up(); let _tear_down = TearDown::new(working_dir.path().to_path_buf()); @@ -125,8 +125,8 @@ fn test_update_with_new_repo_url() { todo!() } +#[ignore = "requires getProcessExecutorMock with expects() command expectations and getMockBuilder PackageInterface mock; no mocking infrastructure exists"] #[test] -#[ignore = "mocks ProcessExecutor/IO and needs an HttpDownloader (curl_multi_init todo!())"] fn test_update_throws_runtime_exception_if_git_command_fails() { let working_dir = set_up(); let _tear_down = TearDown::new(working_dir.path().to_path_buf()); @@ -134,8 +134,8 @@ fn test_update_throws_runtime_exception_if_git_command_fails() { todo!() } +#[ignore = "requires getProcessExecutorMock with expects() command expectations and getMockBuilder PackageInterface mock; no mocking infrastructure exists"] #[test] -#[ignore = "mocks ProcessExecutor/IO and needs an HttpDownloader (curl_multi_init todo!())"] fn test_update_doesnt_throws_runtime_exception_if_git_command_fails_at_first_but_is_able_to_recover() { let working_dir = set_up(); @@ -144,8 +144,8 @@ fn test_update_doesnt_throws_runtime_exception_if_git_command_fails_at_first_but todo!() } +#[ignore = "requires getIOMock with expects() output expectations, getProcessExecutorMock and getMockBuilder PackageInterface mock; no mocking infrastructure exists"] #[test] -#[ignore = "mocks ProcessExecutor/IO and needs an HttpDownloader (curl_multi_init todo!())"] fn test_downgrade_shows_appropriate_message() { let working_dir = set_up(); let _tear_down = TearDown::new(working_dir.path().to_path_buf()); @@ -153,8 +153,8 @@ fn test_downgrade_shows_appropriate_message() { todo!() } +#[ignore = "requires getIOMock with expects() output expectations, getProcessExecutorMock and getMockBuilder PackageInterface mock; no mocking infrastructure exists"] #[test] -#[ignore = "mocks ProcessExecutor/IO and needs an HttpDownloader (curl_multi_init todo!())"] fn test_not_using_downgrading_with_references() { let working_dir = set_up(); let _tear_down = TearDown::new(working_dir.path().to_path_buf()); @@ -162,8 +162,8 @@ fn test_not_using_downgrading_with_references() { todo!() } +#[ignore = "requires getProcessExecutorMock, getMockBuilder Filesystem mock with removeDirectoryAsync expectation and PackageInterface mock; no mocking infrastructure exists"] #[test] -#[ignore = "mocks ProcessExecutor/IO and needs an HttpDownloader (curl_multi_init todo!())"] fn test_remove() { let working_dir = set_up(); let _tear_down = TearDown::new(working_dir.path().to_path_buf()); @@ -171,8 +171,8 @@ fn test_remove() { todo!() } +#[ignore = "requires getDownloaderMock which mocks IOInterface/Filesystem via getMockBuilder; no mocking infrastructure exists"] #[test] -#[ignore = "mocks ProcessExecutor/IO and needs an HttpDownloader (curl_multi_init todo!())"] fn test_get_installation_source() { let working_dir = set_up(); let _tear_down = TearDown::new(working_dir.path().to_path_buf()); diff --git a/crates/shirabe/tests/downloader/hg_downloader_test.rs b/crates/shirabe/tests/downloader/hg_downloader_test.rs index 0bbcdf7..67a13cb 100644 --- a/crates/shirabe/tests/downloader/hg_downloader_test.rs +++ b/crates/shirabe/tests/downloader/hg_downloader_test.rs @@ -35,7 +35,7 @@ impl Drop for TearDown { // curl_multi_init (todo!()), and ProcessExecutor mocking is not available. #[test] -#[ignore = "mocks ProcessExecutor/IO and needs an HttpDownloader (curl_multi_init todo!())"] +#[ignore = "needs PHPUnit mocks of IOInterface/Config/Filesystem/PackageInterface for getDownloaderMock; not available"] fn test_download_for_package_without_source_reference() { let working_dir = set_up(); let _tear_down = TearDown::new(working_dir.path().to_path_buf()); @@ -44,7 +44,7 @@ fn test_download_for_package_without_source_reference() { } #[test] -#[ignore = "mocks ProcessExecutor/IO and needs an HttpDownloader (curl_multi_init todo!())"] +#[ignore = "needs ProcessExecutorMock with expects() command-sequence assertions and PHPUnit PackageInterface mock; not available"] fn test_download() { let working_dir = set_up(); let _tear_down = TearDown::new(working_dir.path().to_path_buf()); @@ -53,7 +53,7 @@ fn test_download() { } #[test] -#[ignore = "mocks ProcessExecutor/IO and needs an HttpDownloader (curl_multi_init todo!())"] +#[ignore = "needs PHPUnit mocks of IOInterface/Config/Filesystem/PackageInterface for getDownloaderMock; not available"] fn test_updatefor_package_without_source_reference() { let working_dir = set_up(); let _tear_down = TearDown::new(working_dir.path().to_path_buf()); @@ -62,7 +62,7 @@ fn test_updatefor_package_without_source_reference() { } #[test] -#[ignore = "mocks ProcessExecutor/IO and needs an HttpDownloader (curl_multi_init todo!())"] +#[ignore = "needs ProcessExecutorMock with expects() command-sequence assertions and PHPUnit PackageInterface mock; not available"] fn test_update() { let working_dir = set_up(); let _tear_down = TearDown::new(working_dir.path().to_path_buf()); @@ -71,7 +71,7 @@ fn test_update() { } #[test] -#[ignore = "mocks ProcessExecutor/IO and needs an HttpDownloader (curl_multi_init todo!())"] +#[ignore = "needs ProcessExecutorMock and PHPUnit Filesystem mock with removeDirectoryAsync expectation; not available"] fn test_remove() { let working_dir = set_up(); let _tear_down = TearDown::new(working_dir.path().to_path_buf()); @@ -80,7 +80,7 @@ fn test_remove() { } #[test] -#[ignore = "mocks ProcessExecutor/IO and needs an HttpDownloader (curl_multi_init todo!())"] +#[ignore = "needs PHPUnit mocks of IOInterface/Config/Filesystem for getDownloaderMock; not available"] fn test_get_installation_source() { let working_dir = set_up(); let _tear_down = TearDown::new(working_dir.path().to_path_buf()); diff --git a/crates/shirabe/tests/downloader/perforce_downloader_test.rs b/crates/shirabe/tests/downloader/perforce_downloader_test.rs index e0f7637..63f1de4 100644 --- a/crates/shirabe/tests/downloader/perforce_downloader_test.rs +++ b/crates/shirabe/tests/downloader/perforce_downloader_test.rs @@ -15,29 +15,29 @@ fn set_up() -> TempDir { // These mock Perforce, the repository config and a Package to drive PerforceDownloader's // initialization and install paths; mocking is not available here. +#[ignore = "requires PHPUnit getMockBuilder mocks of IOInterface/PackageInterface/VcsRepository and ProcessExecutorMock via unported set_up()"] #[test] -#[ignore = "mocks Perforce/repository/Package; mocking is not available"] fn test_init_perforce_instantiates_a_new_perforce_object() { let _test_path = set_up(); todo!() } +#[ignore = "requires PHPUnit getMockBuilder mock of Composer\\Util\\Perforce and expects()->never() verification, unavailable in the Rust port"] #[test] -#[ignore = "mocks Perforce/repository/Package; mocking is not available"] fn test_init_perforce_does_nothing_if_perforce_already_set() { let _test_path = set_up(); todo!() } +#[ignore = "requires PHPUnit getMockBuilder mock of Composer\\Util\\Perforce with expects()->once()->method() expectation verification, unavailable in the Rust port"] #[test] -#[ignore = "mocks Perforce/repository/Package; mocking is not available"] fn test_do_install_with_tag() { let _test_path = set_up(); todo!() } +#[ignore = "requires PHPUnit getMockBuilder mock of Composer\\Util\\Perforce with expects()->once()->method() expectation verification, unavailable in the Rust port"] #[test] -#[ignore = "mocks Perforce/repository/Package; mocking is not available"] fn test_do_install_with_no_tag() { let _test_path = set_up(); todo!() diff --git a/crates/shirabe/tests/downloader/xz_downloader_test.rs b/crates/shirabe/tests/downloader/xz_downloader_test.rs index d01779b..973a726 100644 --- a/crates/shirabe/tests/downloader/xz_downloader_test.rs +++ b/crates/shirabe/tests/downloader/xz_downloader_test.rs @@ -1,9 +1,37 @@ //! ref: composer/tests/Composer/Test/Downloader/XzDownloaderTest.php +use std::cell::RefCell; +use std::rc::Rc; + +use indexmap::IndexMap; +use shirabe::config::Config; +use shirabe::downloader::DownloaderInterface; +use shirabe::downloader::xz_downloader::XzDownloader; +use shirabe::io::IOInterface; +use shirabe::io::null_io::NullIO; +use shirabe::package::handle::{CompletePackageHandle, PackageInterfaceHandle}; +use shirabe::util::HttpDownloader; use shirabe::util::Platform; +use shirabe::util::ProcessExecutor; use shirabe::util::filesystem::Filesystem; +use shirabe::util::r#loop::Loop; +use shirabe_php_shim::PhpMixed; +use shirabe_semver::version_parser::VersionParser; use tempfile::TempDir; +/// ref: TestCase::getPackage (default class CompletePackage) +fn get_package(name: &str, version: &str) -> PackageInterfaceHandle { + let norm_version = VersionParser.normalize(version, None).unwrap(); + CompletePackageHandle::new(name.to_string(), norm_version, version.to_string()).into() +} + +fn run<F: std::future::Future>(future: F) -> F::Output { + tokio::runtime::Builder::new_current_thread() + .build() + .unwrap() + .block_on(future) +} + fn set_up() -> TempDir { if Platform::is_windows() { // markTestSkipped('Skip test on Windows') @@ -40,11 +68,67 @@ impl Drop for TearDown { } } +/// ref: TestCase::getConfig +fn get_config(config_options: IndexMap<String, PhpMixed>, use_environment: bool) -> Config { + let mut config = Config::new(use_environment, None); + let mut merged: IndexMap<String, PhpMixed> = IndexMap::new(); + merged.insert("config".to_string(), PhpMixed::Array(config_options)); + config.merge(&merged, "test"); + config +} + +#[ignore] #[test] -#[ignore = "needs a real HttpDownloader/Loop and an XzDownloader download cycle; HttpDownloader construction reaches curl_multi_init (todo!()) in the php-shim"] fn test_error_messages() { let test_dir = set_up(); let _tear_down = TearDown::new(test_dir.path().to_path_buf()); - let _ = &test_dir; - todo!() + let test_dir = test_dir.path().to_path_buf(); + + let package = get_package("dummy/pkg", "1.0.0"); + let dist_url = format!("file://{}", file!()); + package.set_dist_url(Some(dist_url)); + + let io: Rc<RefCell<dyn IOInterface>> = Rc::new(RefCell::new(NullIO::new())); + let mut config_options: IndexMap<String, PhpMixed> = IndexMap::new(); + config_options.insert( + "vendor-dir".to_string(), + PhpMixed::String(test_dir.to_string_lossy().into_owned()), + ); + let config = Rc::new(RefCell::new(get_config(config_options, false))); + let http_downloader = Rc::new(RefCell::new(HttpDownloader::new( + io.clone(), + config.clone(), + IndexMap::new(), + false, + ))); + let filesystem = Rc::new(RefCell::new(Filesystem::new(None))); + let process = Rc::new(RefCell::new(ProcessExecutor::new(Some(io.clone())))); + let mut downloader = XzDownloader::new( + io, + config, + http_downloader.clone(), + None, + None, + filesystem, + process, + ); + + let install_path = test_dir.join("install-path"); + let install_path = install_path.to_string_lossy().into_owned(); + + let mut loop_ = Loop::new(http_downloader, None); + let promise = Box::pin(async { + downloader + .download3(package.clone(), &install_path, None) + .await + .map(|_| ()) + }); + run(loop_.wait(vec![promise], None)).unwrap(); + let result = run(downloader.install2(package, &install_path)); + + // Download of invalid tarball should throw an exception. + let e = result.expect_err("Download of invalid tarball should throw an exception"); + let re = + regex::Regex::new("(?i)(File format not recognized|Unrecognized archive format)").unwrap(); + assert!(re.is_match(&e.to_string())); } diff --git a/crates/shirabe/tests/downloader/zip_downloader_test.rs b/crates/shirabe/tests/downloader/zip_downloader_test.rs index c22afa1..0bffa89 100644 --- a/crates/shirabe/tests/downloader/zip_downloader_test.rs +++ b/crates/shirabe/tests/downloader/zip_downloader_test.rs @@ -47,8 +47,8 @@ impl Drop for TearDown { // These construct a ZipDownloader with a mocked IO/HttpDownloader/ProcessExecutor and rely // on ZipArchive extraction (todo!() in the php-shim) plus mocked unzip behaviour. +#[ignore = "requires PHPUnit mocks of IOInterface/Config/PackageInterface; set_up() is todo!() and real HttpDownloader reaches curl_multi_init todo!(); no mocking framework"] #[test] -#[ignore = "mocks IO/HttpDownloader/ProcessExecutor and uses ZipArchive (todo!())"] fn test_error_messages() { let set_up = set_up(); let _tear_down = TearDown::new(set_up.test_dir.path().to_path_buf()); @@ -56,8 +56,8 @@ fn test_error_messages() { todo!() } +#[ignore = "requires setPrivateProperty reflection on hasZipArchive/zipArchiveObject, a MockedZipDownloader subclass, and a getMockBuilder('ZipArchive') mock; no mocking/reflection framework"] #[test] -#[ignore = "mocks IO/HttpDownloader/ProcessExecutor and uses ZipArchive (todo!())"] fn test_zip_archive_only_failed() { let set_up = set_up(); let _tear_down = TearDown::new(set_up.test_dir.path().to_path_buf()); @@ -65,8 +65,8 @@ fn test_zip_archive_only_failed() { todo!() } +#[ignore = "requires setPrivateProperty reflection on hasZipArchive/zipArchiveObject, a MockedZipDownloader subclass, and a getMockBuilder('ZipArchive') mock throwing ErrorException; no mocking/reflection framework"] #[test] -#[ignore = "mocks IO/HttpDownloader/ProcessExecutor and uses ZipArchive (todo!())"] fn test_zip_archive_extract_only_failed() { let set_up = set_up(); let _tear_down = TearDown::new(set_up.test_dir.path().to_path_buf()); @@ -74,8 +74,8 @@ fn test_zip_archive_extract_only_failed() { todo!() } +#[ignore = "requires setPrivateProperty reflection on hasZipArchive/zipArchiveObject, a MockedZipDownloader subclass, and a getMockBuilder('ZipArchive') mock; no mocking/reflection framework"] #[test] -#[ignore = "mocks IO/HttpDownloader/ProcessExecutor and uses ZipArchive (todo!())"] fn test_zip_archive_only_good() { let set_up = set_up(); let _tear_down = TearDown::new(set_up.test_dir.path().to_path_buf()); @@ -83,8 +83,8 @@ fn test_zip_archive_only_good() { todo!() } +#[ignore = "requires setPrivateProperty reflection on isWindows/hasZipArchive/unzipCommands, a MockedZipDownloader subclass, and getMockBuilder mocks of Process/ProcessExecutor::executeAsync; no mocking/reflection framework"] #[test] -#[ignore = "mocks IO/HttpDownloader/ProcessExecutor and uses ZipArchive (todo!())"] fn test_system_unzip_only_failed() { let set_up = set_up(); let _tear_down = TearDown::new(set_up.test_dir.path().to_path_buf()); @@ -92,8 +92,8 @@ fn test_system_unzip_only_failed() { todo!() } +#[ignore = "requires setPrivateProperty reflection on isWindows/hasZipArchive/unzipCommands, a MockedZipDownloader subclass, and getMockBuilder mocks of Process/ProcessExecutor::executeAsync; no mocking/reflection framework"] #[test] -#[ignore = "mocks IO/HttpDownloader/ProcessExecutor and uses ZipArchive (todo!())"] fn test_system_unzip_only_good() { let set_up = set_up(); let _tear_down = TearDown::new(set_up.test_dir.path().to_path_buf()); @@ -101,8 +101,8 @@ fn test_system_unzip_only_good() { todo!() } +#[ignore = "requires setPrivateProperty reflection on isWindows/hasZipArchive/zipArchiveObject, a MockedZipDownloader subclass, and getMockBuilder mocks of Process/ProcessExecutor/ZipArchive; no mocking/reflection framework"] #[test] -#[ignore = "mocks IO/HttpDownloader/ProcessExecutor and uses ZipArchive (todo!())"] fn test_non_windows_fallback_good() { let set_up = set_up(); let _tear_down = TearDown::new(set_up.test_dir.path().to_path_buf()); @@ -110,8 +110,8 @@ fn test_non_windows_fallback_good() { todo!() } +#[ignore = "requires setPrivateProperty reflection on isWindows/hasZipArchive/zipArchiveObject, a MockedZipDownloader subclass, and getMockBuilder mocks of Process/ProcessExecutor/ZipArchive; no mocking/reflection framework"] #[test] -#[ignore = "mocks IO/HttpDownloader/ProcessExecutor and uses ZipArchive (todo!())"] fn test_non_windows_fallback_failed() { let set_up = set_up(); let _tear_down = TearDown::new(set_up.test_dir.path().to_path_buf()); diff --git a/crates/shirabe/tests/event_dispatcher/event_dispatcher_test.rs b/crates/shirabe/tests/event_dispatcher/event_dispatcher_test.rs index 8e979a0..2222c3f 100644 --- a/crates/shirabe/tests/event_dispatcher/event_dispatcher_test.rs +++ b/crates/shirabe/tests/event_dispatcher/event_dispatcher_test.rs @@ -18,105 +18,105 @@ impl Drop for TearDown { // listeners (executing CLI/PHP callbacks); mocking and the script-execution machinery are // not available here. #[test] -#[ignore = "mocks Composer/IO/ProcessExecutor and executes script listeners; not ported"] +#[ignore = "requires PHPUnit getMockBuilder onlyMethods(getListeners) override and getIOMock, which have no Rust equivalent"] fn test_listener_exceptions_are_caught() { let _tear_down = TearDown; todo!() } #[test] -#[ignore = "mocks Composer/IO/ProcessExecutor and executes script listeners; not ported"] +#[ignore = "requires getProcessExecutorMock and getMockBuilder onlyMethods(getListeners) override, which have no Rust equivalent"] fn test_dispatcher_can_execute_single_command_line_script() { let _tear_down = TearDown; todo!() } #[test] -#[ignore = "mocks Composer/IO/ProcessExecutor and executes script listeners; not ported"] +#[ignore = "requires getMockBuilder mocks for AutoloadGenerator/RepositoryManager/RootPackageInterface/Event, which have no Rust equivalent"] fn test_dispatcher_pass_dev_mode_to_autoload_generator_for_script_events() { let _tear_down = TearDown; todo!() } #[test] -#[ignore = "mocks Composer/IO/ProcessExecutor and executes script listeners; not ported"] +#[ignore = "requires getMockBuilder mocks for RepositoryManager/InstallationManager and PHP callable listeners ([\\$this, 'method']), which have no Rust equivalent"] fn test_dispatcher_remove_listener() { let _tear_down = TearDown; todo!() } #[test] -#[ignore = "mocks Composer/IO/ProcessExecutor and executes script listeners; not ported"] +#[ignore = "requires getProcessExecutorMock and getMockBuilder onlyMethods(getListeners) override, which have no Rust equivalent"] fn test_dispatcher_can_execute_cli_and_php_in_same_event_script_stack() { let _tear_down = TearDown; todo!() } #[test] -#[ignore = "mocks Composer/IO/ProcessExecutor and executes script listeners; not ported"] +#[ignore = "requires getProcessExecutorMock and getMockBuilder onlyMethods(getListeners) override, which have no Rust equivalent"] fn test_dispatcher_can_put_env() { let _tear_down = TearDown; todo!() } #[test] -#[ignore = "mocks Composer/IO/ProcessExecutor and executes script listeners; not ported"] +#[ignore = "requires getProcessExecutorMock and getMockBuilder onlyMethods(getListeners) override, which have no Rust equivalent"] fn test_dispatcher_appends_dir_bin_on_path_for_every_listener() { let _tear_down = TearDown; todo!() } #[test] -#[ignore = "mocks Composer/IO/ProcessExecutor and executes script listeners; not ported"] +#[ignore = "requires getProcessExecutorMock, getMockBuilder onlyMethods(getListeners) override and ReflectionMethod(getPhpExecCommand), which have no Rust equivalent"] fn test_dispatcher_support_for_additional_args() { let _tear_down = TearDown; todo!() } #[test] -#[ignore = "mocks Composer/IO/ProcessExecutor and executes script listeners; not ported"] +#[ignore = "requires getProcessExecutorMock and getMockBuilder onlyMethods(getListeners) returnCallback override, which have no Rust equivalent"] fn test_dispatcher_can_execute_composer_script_groups() { let _tear_down = TearDown; todo!() } #[test] -#[ignore = "mocks Composer/IO/ProcessExecutor and executes script listeners; not ported"] +#[ignore = "requires getProcessExecutorMock and getMockBuilder onlyMethods(getListeners) returnCallback override, which have no Rust equivalent"] fn test_recursion_in_scripts_names() { let _tear_down = TearDown; todo!() } #[test] -#[ignore = "mocks Composer/IO/ProcessExecutor and executes script listeners; not ported"] +#[ignore = "requires getProcessExecutorMock, getIOMock and getMockBuilder onlyMethods(getListeners) returnCallback override, which have no Rust equivalent"] fn test_dispatcher_detect_infinite_recursion() { let _tear_down = TearDown; todo!() } #[test] -#[ignore = "mocks Composer/IO/ProcessExecutor and executes script listeners; not ported"] +#[ignore = "requires getMockBuilder IOInterface mock with writeError/writeRaw expectations and onlyMethods(getListeners) override, which have no Rust equivalent"] fn test_dispatcher_outputs_command() { let _tear_down = TearDown; todo!() } #[test] -#[ignore = "mocks Composer/IO/ProcessExecutor and executes script listeners; not ported"] +#[ignore = "requires getIOMock and getMockBuilder onlyMethods(getListeners) override, which have no Rust equivalent"] fn test_dispatcher_outputs_error_on_failed_command() { let _tear_down = TearDown; todo!() } #[test] -#[ignore = "mocks Composer/IO/ProcessExecutor and executes script listeners; not ported"] +#[ignore = "requires getProcessExecutorMock, getMockBuilder onlyMethods(getListeners) override and LockTransaction mock, which have no Rust equivalent"] fn test_dispatcher_installer_events() { let _tear_down = TearDown; todo!() } #[test] -#[ignore = "mocks Composer/IO/ProcessExecutor and executes script listeners; not ported"] +#[ignore = "requires getMockBuilder mocks for RootPackageInterface and Event, which have no Rust equivalent"] fn test_dispatcher_doesnt_return_skipped_scripts() { let _tear_down = TearDown; todo!() diff --git a/crates/shirabe/tests/factory_test.rs b/crates/shirabe/tests/factory_test.rs index cfbbaee..04d407b 100644 --- a/crates/shirabe/tests/factory_test.rs +++ b/crates/shirabe/tests/factory_test.rs @@ -16,7 +16,7 @@ impl Drop for TearDown { } #[test] -#[ignore = "mocks an IOInterface with a writeError expectation and a Config returning disable-tls=true; mocking is not available"] +#[ignore = "requires PHPUnit-style mocks: an IOInterface mock verifying writeError is called exactly once with the exact warning, plus a Config mock stubbing get('disable-tls')=>true; no such mock/expectation infrastructure (e.g. mockall) exists"] fn test_default_values_are_as_expected() { let _tear_down = TearDown; diff --git a/crates/shirabe/tests/installed_versions_test.rs b/crates/shirabe/tests/installed_versions_test.rs index d20bc84..6757e03 100644 --- a/crates/shirabe/tests/installed_versions_test.rs +++ b/crates/shirabe/tests/installed_versions_test.rs @@ -1,108 +1,449 @@ //! ref: composer/tests/Composer/Test/InstalledVersionsTest.php -// setUpBeforeClass reflects into ClassLoader::registeredLoaders and the cases load -// installed.php fixtures via InstalledVersions::reload; the reflection and fixture loading -// are not ported. +// setUpBeforeClass reflects into ClassLoader::registeredLoaders to make it seem like no class +// loaders are registered; this is not ported because InstalledVersions::reload already disables +// the multiple-ClassLoader-based checks by setting installedIsLocalDir to false. The PHP setUp +// loads the installed_relative.php fixture via `require`; here the fixture is built inline as an +// IndexMap<String, PhpMixed> with the tmp root substituted for `$dir`. +use indexmap::IndexMap; +use shirabe::installed_versions::InstalledVersions; +use shirabe_php_shim::{PhpMixed, realpath}; +use shirabe_semver::version_parser::VersionParser; use tempfile::TempDir; -fn set_up() -> TempDir { +fn arr(entries: Vec<(&str, PhpMixed)>) -> PhpMixed { + let mut m = IndexMap::new(); + for (k, v) in entries { + m.insert(k.to_string(), v); + } + PhpMixed::Array(m) +} + +fn list(items: Vec<PhpMixed>) -> PhpMixed { + PhpMixed::List(items) +} + +fn s(value: &str) -> PhpMixed { + PhpMixed::String(value.to_string()) +} + +/// Builds the installed_relative.php fixture with `$dir` substituted by `dir`. +fn fixture(dir: &str) -> IndexMap<String, PhpMixed> { + let root_install_path = format!("{}/./", dir); + let mut data = IndexMap::new(); + + data.insert( + "root".to_string(), + arr(vec![ + ("name", s("__root__")), + ("pretty_version", s("dev-master")), + ("version", s("dev-master")), + ("reference", s("sourceref-by-default")), + ("type", s("library")), + ("install_path", s(&root_install_path)), + ("aliases", list(vec![s("1.10.x-dev")])), + ("dev", PhpMixed::Bool(true)), + ]), + ); + + data.insert( + "versions".to_string(), + arr(vec![ + ( + "__root__", + arr(vec![ + ("pretty_version", s("dev-master")), + ("version", s("dev-master")), + ("reference", s("sourceref-by-default")), + ("type", s("library")), + ("install_path", s(&root_install_path)), + ("aliases", list(vec![s("1.10.x-dev")])), + ("dev_requirement", PhpMixed::Bool(false)), + ]), + ), + ( + "a/provider", + arr(vec![ + ("pretty_version", s("1.1")), + ("version", s("1.1.0.0")), + ("reference", s("distref-as-no-source")), + ("type", s("library")), + ("install_path", s(&format!("{}/vendor/a/provider", dir))), + ("aliases", list(vec![])), + ("dev_requirement", PhpMixed::Bool(false)), + ]), + ), + ( + "a/provider2", + arr(vec![ + ("pretty_version", s("1.2")), + ("version", s("1.2.0.0")), + ("reference", s("distref-as-installed-from-dist")), + ("type", s("library")), + ("install_path", s(&format!("{}/vendor/a/provider2", dir))), + ("aliases", list(vec![s("1.4")])), + ("dev_requirement", PhpMixed::Bool(false)), + ]), + ), + ( + "b/replacer", + arr(vec![ + ("pretty_version", s("2.2")), + ("version", s("2.2.0.0")), + ("reference", PhpMixed::Null), + ("type", s("library")), + ("install_path", s(&format!("{}/vendor/b/replacer", dir))), + ("aliases", list(vec![])), + ("dev_requirement", PhpMixed::Bool(false)), + ]), + ), + ( + "c/c", + arr(vec![ + ("pretty_version", s("3.0")), + ("version", s("3.0.0.0")), + ("reference", PhpMixed::Null), + ("type", s("library")), + ("install_path", s("/foo/bar/vendor/c/c")), + ("aliases", list(vec![])), + ("dev_requirement", PhpMixed::Bool(true)), + ]), + ), + ( + "foo/impl", + arr(vec![ + ("dev_requirement", PhpMixed::Bool(false)), + ( + "provided", + list(vec![s("^1.1"), s("1.2"), s("1.4"), s("2.0")]), + ), + ]), + ), + ( + "foo/impl2", + arr(vec![ + ("dev_requirement", PhpMixed::Bool(false)), + ("provided", list(vec![s("2.0")])), + ("replaced", list(vec![s("2.2")])), + ]), + ), + ( + "foo/replaced", + arr(vec![ + ("dev_requirement", PhpMixed::Bool(false)), + ("replaced", list(vec![s("^3.0")])), + ]), + ), + ( + "meta/package", + arr(vec![ + ("pretty_version", s("3.0")), + ("version", s("3.0.0.0")), + ("reference", PhpMixed::Null), + ("type", s("metapackage")), + ("install_path", PhpMixed::Null), + ("aliases", list(vec![])), + ("dev_requirement", PhpMixed::Bool(false)), + ]), + ), + ]), + ); + + data +} + +/// Returns the tmp root (kept alive for the duration of the test) and its path string. +fn set_up() -> (TempDir, String) { let root = TempDir::new().unwrap(); + let dir = root.path().to_str().unwrap().to_string(); - // Loading the installed_relative.php fixture and InstalledVersions::reload are not ported. - todo!(); + InstalledVersions::reload(fixture(&dir)); - #[allow(unreachable_code)] - root + (root, dir) } +#[ignore] #[test] -#[ignore = "needs reflection into ClassLoader::registeredLoaders and installed.php fixtures (not ported)"] fn test_get_installed_packages() { - let _root = set_up(); - todo!() + let (_root, _dir) = set_up(); + + let names = vec![ + "__root__".to_string(), + "a/provider".to_string(), + "a/provider2".to_string(), + "b/replacer".to_string(), + "c/c".to_string(), + "foo/impl".to_string(), + "foo/impl2".to_string(), + "foo/replaced".to_string(), + "meta/package".to_string(), + ]; + assert_eq!(names, InstalledVersions::get_installed_packages()); } +#[ignore] #[test] -#[ignore = "needs reflection into ClassLoader::registeredLoaders and installed.php fixtures (not ported)"] fn test_is_installed() { - let _root = set_up(); - todo!() + let (_root, _dir) = set_up(); + + let cases: Vec<(bool, &str, bool)> = vec![ + (true, "foo/impl", true), + (true, "foo/replaced", true), + (true, "c/c", true), + (false, "c/c", false), + (true, "__root__", true), + (true, "b/replacer", true), + (false, "not/there", true), + (true, "meta/package", true), + ]; + + for (expected, name, include_dev_requirements) in cases { + assert_eq!( + expected, + InstalledVersions::is_installed(name, include_dev_requirements) + ); + } } +#[ignore] #[test] -#[ignore = "needs reflection into ClassLoader::registeredLoaders and installed.php fixtures (not ported)"] fn test_satisfies() { - let _root = set_up(); - todo!() + let (_root, _dir) = set_up(); + + let cases: Vec<(bool, &str, &str)> = vec![ + (true, "foo/impl", "1.5"), + (true, "foo/impl", "1.2"), + (true, "foo/impl", "^1.0"), + (true, "foo/impl", "^3 || ^2"), + (false, "foo/impl", "^3"), + (true, "foo/replaced", "3.5"), + (true, "foo/replaced", "^3.2"), + (false, "foo/replaced", "4.0"), + (true, "c/c", "3.0.0"), + (true, "c/c", "^3"), + (false, "c/c", "^3.1"), + (true, "__root__", "dev-master"), + (true, "__root__", "^1.10"), + (false, "__root__", "^2"), + (true, "b/replacer", "^2.1"), + (false, "b/replacer", "^2.3"), + (true, "a/provider2", "^1.2"), + (true, "a/provider2", "^1.4"), + (false, "a/provider2", "^1.5"), + ]; + + for (expected, name, constraint) in cases { + assert_eq!( + expected, + InstalledVersions::satisfies(&VersionParser, name, Some(constraint)).unwrap() + ); + } } +#[ignore] #[test] -#[ignore = "needs reflection into ClassLoader::registeredLoaders and installed.php fixtures (not ported)"] fn test_get_version_ranges() { - let _root = set_up(); - todo!() + let (_root, _dir) = set_up(); + + let cases: Vec<(&str, &str)> = vec![ + ("dev-master || 1.10.x-dev", "__root__"), + ("^1.1 || 1.2 || 1.4 || 2.0", "foo/impl"), + ("2.2 || 2.0", "foo/impl2"), + ("^3.0", "foo/replaced"), + ("1.1", "a/provider"), + ("1.2 || 1.4", "a/provider2"), + ("2.2", "b/replacer"), + ("3.0", "c/c"), + ]; + + for (expected, name) in cases { + assert_eq!( + expected, + InstalledVersions::get_version_ranges(name).unwrap() + ); + } } +#[ignore] #[test] -#[ignore = "needs reflection into ClassLoader::registeredLoaders and installed.php fixtures (not ported)"] fn test_get_version() { - let _root = set_up(); - todo!() + let (_root, _dir) = set_up(); + + let cases: Vec<(Option<&str>, &str)> = vec![ + (Some("dev-master"), "__root__"), + (None, "foo/impl"), + (None, "foo/impl2"), + (None, "foo/replaced"), + (Some("1.1.0.0"), "a/provider"), + (Some("1.2.0.0"), "a/provider2"), + (Some("2.2.0.0"), "b/replacer"), + (Some("3.0.0.0"), "c/c"), + ]; + + for (expected, name) in cases { + assert_eq!( + expected.map(|s| s.to_string()), + InstalledVersions::get_version(name).unwrap() + ); + } } +#[ignore] #[test] -#[ignore = "needs reflection into ClassLoader::registeredLoaders and installed.php fixtures (not ported)"] fn test_get_pretty_version() { - let _root = set_up(); - todo!() + let (_root, _dir) = set_up(); + + let cases: Vec<(Option<&str>, &str)> = vec![ + (Some("dev-master"), "__root__"), + (None, "foo/impl"), + (None, "foo/impl2"), + (None, "foo/replaced"), + (Some("1.1"), "a/provider"), + (Some("1.2"), "a/provider2"), + (Some("2.2"), "b/replacer"), + (Some("3.0"), "c/c"), + ]; + + for (expected, name) in cases { + assert_eq!( + expected.map(|s| s.to_string()), + InstalledVersions::get_pretty_version(name).unwrap() + ); + } } +#[ignore] #[test] -#[ignore = "needs reflection into ClassLoader::registeredLoaders and installed.php fixtures (not ported)"] fn test_get_version_out_of_bounds() { - let _root = set_up(); - todo!() + let (_root, _dir) = set_up(); + + assert!(InstalledVersions::get_version("not/installed").is_err()); } +#[ignore] #[test] -#[ignore = "needs reflection into ClassLoader::registeredLoaders and installed.php fixtures (not ported)"] fn test_get_root_package() { - let _root = set_up(); - todo!() + let (_root, dir) = set_up(); + + let expected = { + let mut m = IndexMap::new(); + m.insert("name".to_string(), s("__root__")); + m.insert("pretty_version".to_string(), s("dev-master")); + m.insert("version".to_string(), s("dev-master")); + m.insert("reference".to_string(), s("sourceref-by-default")); + m.insert("type".to_string(), s("library")); + m.insert("install_path".to_string(), s(&format!("{}/./", dir))); + m.insert("aliases".to_string(), list(vec![s("1.10.x-dev")])); + m.insert("dev".to_string(), PhpMixed::Bool(true)); + m + }; + + assert_eq!(expected, InstalledVersions::get_root_package()); } +#[ignore = "InstalledVersions::get_raw_data not ported (only get_all_raw_data exists)"] #[test] -#[ignore = "needs reflection into ClassLoader::registeredLoaders and installed.php fixtures (not ported)"] fn test_get_raw_data() { let _root = set_up(); todo!() } +#[ignore] #[test] -#[ignore = "needs reflection into ClassLoader::registeredLoaders and installed.php fixtures (not ported)"] fn test_get_reference() { - let _root = set_up(); - todo!() + let (_root, _dir) = set_up(); + + let cases: Vec<(Option<&str>, &str)> = vec![ + (Some("sourceref-by-default"), "__root__"), + (None, "foo/impl"), + (None, "foo/impl2"), + (None, "foo/replaced"), + (Some("distref-as-no-source"), "a/provider"), + (Some("distref-as-installed-from-dist"), "a/provider2"), + (None, "b/replacer"), + (None, "c/c"), + ]; + + for (expected, name) in cases { + assert_eq!( + expected.map(|s| s.to_string()), + InstalledVersions::get_reference(name).unwrap() + ); + } } +#[ignore] #[test] -#[ignore = "needs reflection into ClassLoader::registeredLoaders and installed.php fixtures (not ported)"] fn test_get_installed_packages_by_type() { - let _root = set_up(); - todo!() + let (_root, _dir) = set_up(); + + let names = vec![ + "__root__".to_string(), + "a/provider".to_string(), + "a/provider2".to_string(), + "b/replacer".to_string(), + "c/c".to_string(), + ]; + + assert_eq!( + names, + InstalledVersions::get_installed_packages_by_type("library") + ); } +#[ignore] #[test] -#[ignore = "needs reflection into ClassLoader::registeredLoaders and installed.php fixtures (not ported)"] fn test_get_install_path() { - let _root = set_up(); - todo!() + let (_root, dir) = set_up(); + + assert_eq!( + realpath(&dir), + realpath( + &InstalledVersions::get_install_path("__root__") + .unwrap() + .unwrap() + ) + ); + assert_eq!( + Some("/foo/bar/vendor/c/c".to_string()), + InstalledVersions::get_install_path("c/c").unwrap() + ); + assert_eq!( + None, + InstalledVersions::get_install_path("foo/impl").unwrap() + ); } +#[ignore] #[test] -#[ignore = "needs reflection into ClassLoader::registeredLoaders and installed.php fixtures (not ported)"] fn test_with_class_loader_loaded() { - let _root = set_up(); - todo!() + let (_root, _dir) = set_up(); + + // The reflection into ClassLoader::registeredLoaders is not ported; installedIsLocalDir is + // toggled directly via the exposed setter to mirror the PHP reflection on it. + InstalledVersions::set_installed_is_local_dir(true); + + assert!(!InstalledVersions::is_installed("foo/bar", true)); + + let reload_data = { + let mut m = IndexMap::new(); + m.insert( + "root".to_string(), + PhpMixed::Array(InstalledVersions::get_root_package()), + ); + m.insert( + "versions".to_string(), + arr(vec![( + "foo/bar", + arr(vec![ + ("version", s("1.0.0")), + ("dev_requirement", PhpMixed::Bool(false)), + ]), + )]), + ); + m + }; + InstalledVersions::reload(reload_data); + assert!(InstalledVersions::is_installed("foo/bar", true)); } diff --git a/crates/shirabe/tests/installer/binary_installer_test.rs b/crates/shirabe/tests/installer/binary_installer_test.rs index 0a22243..7bd5eea 100644 --- a/crates/shirabe/tests/installer/binary_installer_test.rs +++ b/crates/shirabe/tests/installer/binary_installer_test.rs @@ -24,7 +24,7 @@ impl Drop for TearDown { // program's output. It needs a real PHP runtime, the binary-proxy generation, and a // mocked Package's getBinaries(), none of which are available here. #[test] -#[ignore = "installs and executes a PHP binary via ProcessExecutor (needs a real PHP runtime and binary proxies) and mocks a Package"] +#[ignore = "needs PHPUnit getMockBuilder mocks of IOInterface and Package::getBinaries() plus a real PHP runtime to execute the installed binary and assert its output"] fn test_install_and_exec_binary_with_full_compat() { todo!() } diff --git a/crates/shirabe/tests/installer/installation_manager_test.rs b/crates/shirabe/tests/installer/installation_manager_test.rs index b44a814..a12381b 100644 --- a/crates/shirabe/tests/installer/installation_manager_test.rs +++ b/crates/shirabe/tests/installer/installation_manager_test.rs @@ -8,50 +8,50 @@ fn set_up() { // These mock individual installers, the repository and IO to drive InstallationManager's // add/execute/install/update/uninstall logic; mocking is not available here. +#[ignore = "requires PHPUnit mocks of InstallerInterface/IOInterface/Loop with expects() call-count assertions"] #[test] -#[ignore = "mocks installers/repository/IO to drive InstallationManager; mocking is not available"] fn test_add_get_installer() { todo!() } +#[ignore = "requires PHPUnit mocks of InstallerInterface/IOInterface/Loop with expects() call-count assertions"] #[test] -#[ignore = "mocks installers/repository/IO to drive InstallationManager; mocking is not available"] fn test_add_remove_installer() { todo!() } +#[ignore = "requires partial PHPUnit mock of InstallationManager (onlyMethods install/update/uninstall) and PackageInterface mock"] #[test] -#[ignore = "mocks installers/repository/IO to drive InstallationManager; mocking is not available"] fn test_execute() { todo!() } +#[ignore = "requires PHPUnit mocks of InstallerInterface/PackageInterface with expects() call-count assertions"] #[test] -#[ignore = "mocks installers/repository/IO to drive InstallationManager; mocking is not available"] fn test_install() { todo!() } +#[ignore = "requires PHPUnit mocks of InstallerInterface/PackageInterface with expects() call-count assertions"] #[test] -#[ignore = "mocks installers/repository/IO to drive InstallationManager; mocking is not available"] fn test_update_with_equal_types() { todo!() } +#[ignore = "requires PHPUnit mocks of InstallerInterface/PackageInterface with expects() call-count assertions"] #[test] -#[ignore = "mocks installers/repository/IO to drive InstallationManager; mocking is not available"] fn test_update_with_not_equal_types() { todo!() } +#[ignore = "requires PHPUnit mocks of InstallerInterface/PackageInterface with expects() call-count assertions"] #[test] -#[ignore = "mocks installers/repository/IO to drive InstallationManager; mocking is not available"] fn test_uninstall() { todo!() } +#[ignore = "requires PHPUnit mock of LibraryInstaller and PackageInterface with expects() call-count assertions"] #[test] -#[ignore = "mocks installers/repository/IO to drive InstallationManager; mocking is not available"] fn test_install_binary() { todo!() } diff --git a/crates/shirabe/tests/installer/library_installer_test.rs b/crates/shirabe/tests/installer/library_installer_test.rs index 2182962..62a2c0c 100644 --- a/crates/shirabe/tests/installer/library_installer_test.rs +++ b/crates/shirabe/tests/installer/library_installer_test.rs @@ -1,77 +1,188 @@ //! ref: composer/tests/Composer/Test/Installer/LibraryInstallerTest.php -/// Sets up a Composer/Config over root/vendor/bin temp dirs plus mocked -/// DownloadManager/repository/IO. The temp-dir helpers and the mocks are not -/// available here, so this remains a stub. -fn set_up() { - todo!() -} +use std::cell::RefCell; +use std::fs; +use std::rc::Rc; -/// Removes the root dir created by `set_up`, which is itself a stub. -fn tear_down() { - todo!() +use indexmap::IndexMap; +use tempfile::TempDir; + +use shirabe::composer::{ + ComposerHandle, PartialComposerHandle, PartialComposerWeakHandle, PartialOrFullComposer, +}; +use shirabe::config::Config; +use shirabe::downloader::DownloadManager; +use shirabe::installer::{InstallerInterface, LibraryInstaller}; +use shirabe::io::IOInterface; +use shirabe::io::null_io::NullIO; +use shirabe::repository::InstalledArrayRepository; +use shirabe::repository::WritableRepositoryInterface; +use shirabe::util::filesystem::Filesystem; +use shirabe_php_shim::PhpMixed; + +use crate::test_case::get_package; + +/// Mirror of setUp(): builds the Composer/Config over temp root/vendor/bin dirs +/// plus a real DownloadManager and a NullIO. The `_composer_rc` keeps the inner +/// Rc alive for the duration of the test since LibraryInstaller only holds a weak +/// handle. +struct SetUp { + root: TempDir, + vendor_dir: String, + bin_dir: String, + io: Rc<RefCell<dyn IOInterface>>, + composer: PartialComposerWeakHandle, + fs: Filesystem, + _composer_rc: ComposerHandle, } -struct TearDown; +fn set_up() -> SetUp { + let mut fs = Filesystem::new(None); + + let root = TempDir::new().unwrap(); + let root_dir = fs::canonicalize(root.path()) + .unwrap() + .to_string_lossy() + .into_owned(); + + let vendor_dir = format!("{}/vendor", root_dir); + fs::create_dir_all(&vendor_dir).unwrap(); -impl Drop for TearDown { - fn drop(&mut self) { - tear_down(); + let bin_dir = format!("{}/bin", root_dir); + fs::create_dir_all(&bin_dir).unwrap(); + + let mut config = Config::new(false, None); + let mut config_section: IndexMap<String, PhpMixed> = IndexMap::new(); + config_section.insert( + "vendor-dir".to_string(), + PhpMixed::String(vendor_dir.clone()), + ); + config_section.insert("bin-dir".to_string(), PhpMixed::String(bin_dir.clone())); + let mut merged: IndexMap<String, PhpMixed> = IndexMap::new(); + merged.insert("config".to_string(), PhpMixed::Array(config_section)); + config.merge(&merged, Config::SOURCE_UNKNOWN); + let config_rc = Rc::new(RefCell::new(config)); + + let io: Rc<RefCell<dyn IOInterface>> = Rc::new(RefCell::new(NullIO::new())); + + let dm = DownloadManager::new(io.clone(), false, None); + let dm_rc = Rc::new(RefCell::new(dm)); + + let composer_rc = Rc::new(RefCell::new(PartialOrFullComposer::new_full())); + let composer = ComposerHandle::from_rc_unchecked(composer_rc.clone()); + composer.borrow_mut().set_config(config_rc); + composer.borrow_mut().set_download_manager(dm_rc); + + let weak = PartialComposerHandle::from_rc(composer_rc).downgrade(); + + SetUp { + root, + vendor_dir, + bin_dir, + io, + composer: weak, + fs, + _composer_rc: composer, } } -// These construct a LibraryInstaller over a temp dir with a mocked IO/Filesystem/repository -// and mocked packages to drive install/update/uninstall and path resolution. +fn tear_down(setup: &mut SetUp) { + let root = setup.root.path().to_path_buf(); + setup.fs.remove_directory(&root).ok(); +} + +#[ignore] #[test] -#[ignore = "mocks IO/Filesystem/repository and packages to drive LibraryInstaller; mocking is not available"] fn test_installer_creation_should_not_create_vendor_directory() { - todo!() + let mut setup = set_up(); + setup.fs.remove_directory(&setup.vendor_dir).unwrap(); + + LibraryInstaller::new(setup.io.clone(), setup.composer.clone(), None, None, None); + assert!(!std::path::Path::new(&setup.vendor_dir).exists()); + + tear_down(&mut setup); } +#[ignore] #[test] -#[ignore = "mocks IO/Filesystem/repository and packages to drive LibraryInstaller; mocking is not available"] fn test_installer_creation_should_not_create_bin_directory() { - todo!() + let mut setup = set_up(); + setup.fs.remove_directory(&setup.bin_dir).unwrap(); + + LibraryInstaller::new(setup.io.clone(), setup.composer.clone(), None, None, None); + assert!(!std::path::Path::new(&setup.bin_dir).exists()); + + tear_down(&mut setup); } +#[ignore] #[test] -#[ignore = "mocks IO/Filesystem/repository and packages to drive LibraryInstaller; mocking is not available"] fn test_is_installed() { - todo!() + let mut setup = set_up(); + let mut library = + LibraryInstaller::new(setup.io.clone(), setup.composer.clone(), None, None, None); + let package = get_package("test/pkg", "1.0.0"); + + let mut repository = InstalledArrayRepository::new().unwrap(); + assert!(!library.is_installed(&repository, package.clone())); + + // package being in repo is not enough to be installed + repository.add_package(package.clone()).unwrap(); + assert!(!library.is_installed(&repository, package.clone())); + + // package being in repo and vendor/pkg/foo dir present means it is seen as installed + let pkg_dir = format!("{}/{}", setup.vendor_dir, package.get_pretty_name()); + fs::create_dir_all(&pkg_dir).unwrap(); + assert!(library.is_installed(&repository, package.clone())); + + repository.remove_package(package.clone()).unwrap(); + assert!(!library.is_installed(&repository, package)); + + tear_down(&mut setup); } #[test] -#[ignore = "mocks IO/Filesystem/repository and packages to drive LibraryInstaller; mocking is not available"] +#[ignore = "requires PHPUnit mock of DownloadManager (expects(once)->method('install')->will(resolve(null))) and InstalledRepositoryInterface (expects(once)->addPackage); a real DownloadManager would attempt a download"] fn test_install() { todo!() } #[test] -#[ignore = "mocks IO/Filesystem/repository and packages to drive LibraryInstaller; mocking is not available"] +#[ignore = "requires PHPUnit mocks (Filesystem::rename, DownloadManager::update, repository hasPackage/add/remove with onConsecutiveCalls) and Package::setTargetDir, which is not exposed on PackageInterfaceHandle"] fn test_update() { todo!() } #[test] -#[ignore = "mocks IO/Filesystem/repository and packages to drive LibraryInstaller; mocking is not available"] +#[ignore = "requires PHPUnit mock of DownloadManager (expects(once)->method('remove')->will(resolve(null))) and InstalledRepositoryInterface (hasPackage onConsecutiveCalls, removePackage)"] fn test_uninstall() { todo!() } +#[ignore] #[test] -#[ignore = "mocks IO/Filesystem/repository and packages to drive LibraryInstaller; mocking is not available"] fn test_get_install_path_without_target_dir() { - todo!() + let mut setup = set_up(); + let mut library = + LibraryInstaller::new(setup.io.clone(), setup.composer.clone(), None, None, None); + let package = get_package("Vendor/Pkg", "1.0.0"); + + assert_eq!( + format!("{}/{}", setup.vendor_dir, package.get_pretty_name()), + library.get_install_path(package).unwrap() + ); + + tear_down(&mut setup); } #[test] -#[ignore = "mocks IO/Filesystem/repository and packages to drive LibraryInstaller; mocking is not available"] +#[ignore = "Package::setTargetDir is not exposed on PackageInterfaceHandle, so the target-dir cannot be set on the test package"] fn test_get_install_path_with_target_dir() { todo!() } #[test] -#[ignore = "mocks IO/Filesystem/repository and packages to drive LibraryInstaller; mocking is not available"] +#[ignore = "requires PHPUnit mock of BinaryInstaller (expects(never)->removeBinaries, expects(once)->installBinaries) injected via LibraryInstaller's binaryInstaller argument"] fn test_ensure_binaries_installed() { todo!() } diff --git a/crates/shirabe/tests/installer/suggested_packages_reporter_test.rs b/crates/shirabe/tests/installer/suggested_packages_reporter_test.rs index fbdb16f..bd7672e 100644 --- a/crates/shirabe/tests/installer/suggested_packages_reporter_test.rs +++ b/crates/shirabe/tests/installer/suggested_packages_reporter_test.rs @@ -1,75 +1,123 @@ //! ref: composer/tests/Composer/Test/Installer/SuggestedPackagesReporterTest.php +use std::cell::RefCell; +use std::rc::Rc; + +use indexmap::IndexMap; +use shirabe::installer::SuggestedPackagesReporter; +use shirabe::io::IOInterface; +use shirabe::io::null_io::NullIO; + /// Builds an IO mock and a SuggestedPackagesReporter over it. The IO mock /// (`getIOMock`) is not available here, so this remains a stub. fn set_up() { todo!() } +/// ref: SuggestedPackagesReporterTest::getSuggestedPackageArray +fn get_suggested_package_array() -> IndexMap<String, String> { + let mut entry = IndexMap::new(); + entry.insert("source".to_string(), "a".to_string()); + entry.insert("target".to_string(), "b".to_string()); + entry.insert("reason".to_string(), "c".to_string()); + entry +} + +fn reporter() -> SuggestedPackagesReporter { + let io: Rc<RefCell<dyn IOInterface>> = Rc::new(RefCell::new(NullIO::new())); + SuggestedPackagesReporter::new(io) +} + // These construct a SuggestedPackagesReporter with a mocked IO and assert its accumulated // suggestions and formatted output; mocking is not available here. +#[ignore = "asserts IO mock output via getIOMock()->expects(); no IO mocking infrastructure exists and BufferIO::new/get_output are todo!()"] #[test] -#[ignore = "mocks IO to drive SuggestedPackagesReporter output; mocking is not available"] fn test_constructor() { todo!() } +#[ignore] #[test] -#[ignore = "mocks IO to drive SuggestedPackagesReporter output; mocking is not available"] fn test_get_packages_empty_by_default() { - todo!() + let reporter = reporter(); + assert!(reporter.get_packages().is_empty()); } +#[ignore] #[test] -#[ignore = "mocks IO to drive SuggestedPackagesReporter output; mocking is not available"] fn test_get_packages() { - todo!() + let suggested_package = get_suggested_package_array(); + let mut reporter = reporter(); + reporter.add_package( + suggested_package["source"].clone(), + suggested_package["target"].clone(), + suggested_package["reason"].clone(), + ); + assert_eq!(&vec![suggested_package], reporter.get_packages()); } +#[ignore] #[test] -#[ignore = "mocks IO to drive SuggestedPackagesReporter output; mocking is not available"] fn test_add_package_appends() { - todo!() + let suggested_package_a = get_suggested_package_array(); + let mut suggested_package_b = get_suggested_package_array(); + suggested_package_b.insert("source".to_string(), "different source".to_string()); + suggested_package_b.insert("reason".to_string(), "different reason".to_string()); + let mut reporter = reporter(); + reporter.add_package( + suggested_package_a["source"].clone(), + suggested_package_a["target"].clone(), + suggested_package_a["reason"].clone(), + ); + reporter.add_package( + suggested_package_b["source"].clone(), + suggested_package_b["target"].clone(), + suggested_package_b["reason"].clone(), + ); + assert_eq!( + &vec![suggested_package_a, suggested_package_b], + reporter.get_packages() + ); } +#[ignore = "addSuggestionsFromPackage test mocks Package::getSuggests; set_suggests only exists on RootPackageHandle, so the non-root Package fixture with suggests cannot be expressed"] #[test] -#[ignore = "mocks IO to drive SuggestedPackagesReporter output; mocking is not available"] fn test_add_suggestions_from_package() { todo!() } +#[ignore = "asserts IO mock output via getIOMock()->expects(); no IO mocking infrastructure exists and BufferIO::new/get_output are todo!()"] #[test] -#[ignore = "mocks IO to drive SuggestedPackagesReporter output; mocking is not available"] fn test_output() { todo!() } +#[ignore = "asserts IO mock output via getIOMock()->expects(); no IO mocking infrastructure exists and BufferIO::new/get_output are todo!()"] #[test] -#[ignore = "mocks IO to drive SuggestedPackagesReporter output; mocking is not available"] fn test_output_with_no_suggestion_reason() { todo!() } +#[ignore = "asserts IO mock output via getIOMock()->expects(); no IO mocking infrastructure exists and BufferIO::new/get_output are todo!()"] #[test] -#[ignore = "mocks IO to drive SuggestedPackagesReporter output; mocking is not available"] fn test_output_ignores_formatting() { todo!() } +#[ignore = "asserts IO mock output via getIOMock()->expects(); no IO mocking infrastructure exists and BufferIO::new/get_output are todo!()"] #[test] -#[ignore = "mocks IO to drive SuggestedPackagesReporter output; mocking is not available"] fn test_output_multiple_packages() { todo!() } +#[ignore = "asserts IO mock output via getIOMock()->expects() and uses getMockBuilder mocks of InstalledRepository/PackageInterface; no mocking infrastructure exists"] #[test] -#[ignore = "mocks IO to drive SuggestedPackagesReporter output; mocking is not available"] fn test_output_skip_installed_packages() { todo!() } +#[ignore = "uses getMockBuilder mock of InstalledRepository with ->expects($this->exactly(0)) call-count assertion; no mocking infrastructure exists"] #[test] -#[ignore = "mocks IO to drive SuggestedPackagesReporter output; mocking is not available"] fn test_output_not_getting_installed_packages_when_no_suggestions() { todo!() } diff --git a/crates/shirabe/tests/installer_test.rs b/crates/shirabe/tests/installer_test.rs index e4f6c6f..e2d4939 100644 --- a/crates/shirabe/tests/installer_test.rs +++ b/crates/shirabe/tests/installer_test.rs @@ -22,28 +22,28 @@ impl Drop for TearDown { } #[test] -#[ignore = "not yet ported (end-to-end Installer integration over fixtures; constraint parsing uses a look-around regex)"] +#[ignore = "requires PHPUnit getMockBuilder mocks of DownloadManager/Config/EventDispatcher/HttpDownloader/JsonFile/AutoloadGenerator plus an unported InstallationManagerMock and the provideInstaller data provider; no mocking infrastructure exists"] fn test_installer() { let _tear_down = TearDown; todo!() } #[test] -#[ignore = "not yet ported (end-to-end Installer integration over fixtures; constraint parsing uses a look-around regex)"] +#[ignore = "delegates to unported do_test_integration which needs FactoryMock, InstalledFilesystemRepositoryMock, the loadIntegrationTests .test-fixture loader and a symfony console Application; none exist in the Rust port"] fn test_slow_integration() { let _tear_down = TearDown; todo!() } #[test] -#[ignore = "not yet ported (end-to-end Installer integration over fixtures; constraint parsing uses a look-around regex)"] +#[ignore = "delegates to unported do_test_integration which needs FactoryMock, InstalledFilesystemRepositoryMock, the loadIntegrationTests .test-fixture loader and a symfony console Application; none exist in the Rust port"] fn test_integration_with_pool_optimizer() { let _tear_down = TearDown; todo!() } #[test] -#[ignore = "not yet ported (end-to-end Installer integration over fixtures; constraint parsing uses a look-around regex)"] +#[ignore = "delegates to unported do_test_integration which needs FactoryMock, InstalledFilesystemRepositoryMock, the loadIntegrationTests .test-fixture loader and a symfony console Application; none exist in the Rust port"] fn test_integration_with_raw_pool() { let _tear_down = TearDown; todo!() diff --git a/crates/shirabe/tests/io/console_io_test.rs b/crates/shirabe/tests/io/console_io_test.rs index a931a8b..c1adbd4 100644 --- a/crates/shirabe/tests/io/console_io_test.rs +++ b/crates/shirabe/tests/io/console_io_test.rs @@ -2,80 +2,128 @@ // These mock Symfony's InputInterface/OutputInterface/HelperSet (and QuestionHelper) to // drive ConsoleIO; those console abstractions are not ported. + +use indexmap::IndexMap; +use shirabe::io::ConsoleIO; +use shirabe::io::IOInterfaceImmutable; +use shirabe::io::IOInterfaceMutable; +use shirabe_external_packages::symfony::console::helper::HelperSet; +use shirabe_external_packages::symfony::console::input::array_input::ArrayInput; +use shirabe_external_packages::symfony::console::input::input_interface::InputInterface; +use shirabe_external_packages::symfony::console::output::buffered_output::BufferedOutput; +use shirabe_external_packages::symfony::console::output::output_interface::OutputInterface; +use std::cell::RefCell; +use std::rc::Rc; + +/// Builds a ConsoleIO backed by real, side-effect-free Input/Output/HelperSet. PHP uses +/// PHPUnit mocks here, but for tests that exercise only the authentication map they carry no +/// expectations, so concrete implementations are an exact substitute. +fn make_console_io() -> ConsoleIO { + let input: Rc<RefCell<dyn InputInterface>> = + Rc::new(RefCell::new(ArrayInput::new(vec![], None).unwrap())); + let output: Rc<RefCell<dyn OutputInterface>> = + Rc::new(RefCell::new(BufferedOutput::new(None, false, None))); + let helper_set = HelperSet::default(); + ConsoleIO::new(input, output, helper_set) +} + +#[ignore = "requires a PHPUnit mock of InputInterface::isInteractive with willReturnOnConsecutiveCalls; no mocking framework"] #[test] -#[ignore = "mocks Symfony Input/Output/HelperSet to drive ConsoleIO; not ported"] fn test_is_interactive() { todo!() } +#[ignore = "requires a PHPUnit mock of OutputInterface with expects()->method('write')->with() expectation verification; no mocking framework"] #[test] -#[ignore = "mocks Symfony Input/Output/HelperSet to drive ConsoleIO; not ported"] fn test_write() { todo!() } +#[ignore = "requires a PHPUnit mock of ConsoleOutputInterface with getErrorOutput/write expectation verification; no mocking framework"] #[test] -#[ignore = "mocks Symfony Input/Output/HelperSet to drive ConsoleIO; not ported"] fn test_write_error() { todo!() } +#[ignore = "requires a PHPUnit mock of OutputInterface with a write() callback expectation verification; no mocking framework"] #[test] -#[ignore = "mocks Symfony Input/Output/HelperSet to drive ConsoleIO; not ported"] fn test_write_with_multiple_line_string_when_debugging() { todo!() } +#[ignore = "requires a PHPUnit mock of OutputInterface with willReturnCallback series expectation verification; no mocking framework"] #[test] -#[ignore = "mocks Symfony Input/Output/HelperSet to drive ConsoleIO; not ported"] fn test_overwrite() { todo!() } +#[ignore = "requires a PHPUnit mock of QuestionHelper/HelperSet with ask/get expectation verification; no mocking framework"] #[test] -#[ignore = "mocks Symfony Input/Output/HelperSet to drive ConsoleIO; not ported"] fn test_ask() { todo!() } +#[ignore = "requires a PHPUnit mock of QuestionHelper/HelperSet with ask/get expectation verification; no mocking framework"] #[test] -#[ignore = "mocks Symfony Input/Output/HelperSet to drive ConsoleIO; not ported"] fn test_ask_confirmation() { todo!() } +#[ignore = "requires a PHPUnit mock of QuestionHelper/HelperSet with ask/get expectation verification; no mocking framework"] #[test] -#[ignore = "mocks Symfony Input/Output/HelperSet to drive ConsoleIO; not ported"] fn test_ask_and_validate() { todo!() } +#[ignore = "requires a PHPUnit mock of QuestionHelper/HelperSet with ask/get expectation verification; no mocking framework"] #[test] -#[ignore = "mocks Symfony Input/Output/HelperSet to drive ConsoleIO; not ported"] fn test_select() { todo!() } +#[ignore] #[test] -#[ignore = "mocks Symfony Input/Output/HelperSet to drive ConsoleIO; not ported"] fn test_set_and_get_authentication() { - todo!() + let mut console_io = make_console_io(); + console_io.set_authentication( + "repoName".to_string(), + "l3l0".to_string(), + Some("passwd".to_string()), + ); + + let mut expected: IndexMap<String, Option<String>> = IndexMap::new(); + expected.insert("username".to_string(), Some("l3l0".to_string())); + expected.insert("password".to_string(), Some("passwd".to_string())); + assert_eq!(expected, console_io.get_authentication("repoName")); } +#[ignore] #[test] -#[ignore = "mocks Symfony Input/Output/HelperSet to drive ConsoleIO; not ported"] fn test_get_authentication_when_did_not_set() { - todo!() + let console_io = make_console_io(); + + let mut expected: IndexMap<String, Option<String>> = IndexMap::new(); + expected.insert("username".to_string(), None); + expected.insert("password".to_string(), None); + assert_eq!(expected, console_io.get_authentication("repoName")); } +#[ignore] #[test] -#[ignore = "mocks Symfony Input/Output/HelperSet to drive ConsoleIO; not ported"] fn test_has_authentication() { - todo!() + let mut console_io = make_console_io(); + console_io.set_authentication( + "repoName".to_string(), + "l3l0".to_string(), + Some("passwd".to_string()), + ); + + assert!(console_io.has_authentication("repoName")); + assert!(!console_io.has_authentication("repoName2")); } +#[ignore = "data provider includes malformed-UTF-8 inputs (e.g. \\xFF, \\xC3\\x28); sanitize() takes PhpMixed::String which is UTF-8-only and cannot carry invalid bytes, so those cases are unrepresentable"] #[test] -#[ignore = "mocks Symfony Input/Output/HelperSet to drive ConsoleIO; not ported"] fn test_sanitize() { todo!() } diff --git a/crates/shirabe/tests/json/composer_schema_test.rs b/crates/shirabe/tests/json/composer_schema_test.rs index 1679b5d..08415f6 100644 --- a/crates/shirabe/tests/json/composer_schema_test.rs +++ b/crates/shirabe/tests/json/composer_schema_test.rs @@ -2,32 +2,32 @@ // These validate documents against the bundled composer-schema.json via JsonFile's // json-schema validator, which is not ported. +#[ignore = "JsonSchema Validator::get_errors returns Vec<String>; cannot represent structured property/message/constraint error arrays asserted here, and Validator::validate is not ported"] #[test] -#[ignore = "needs JsonFile schema validation against the bundled composer-schema.json (not ported)"] fn test_name_pattern() { todo!() } +#[ignore = "JsonSchema Validator::get_errors returns Vec<String>; cannot represent structured property/message/constraint error arrays asserted here, and Validator::validate is not ported"] #[test] -#[ignore = "needs JsonFile schema validation against the bundled composer-schema.json (not ported)"] fn test_version_pattern() { todo!() } +#[ignore = "JsonSchema Validator::get_errors returns Vec<String>; cannot represent structured property/message/constraint error arrays asserted here, and Validator::validate is not ported"] #[test] -#[ignore = "needs JsonFile schema validation against the bundled composer-schema.json (not ported)"] fn test_optional_abandoned_property() { todo!() } +#[ignore = "JsonSchema Validator::get_errors returns Vec<String>; cannot represent structured property/message/constraint error arrays asserted here, and Validator::validate is not ported"] #[test] -#[ignore = "needs JsonFile schema validation against the bundled composer-schema.json (not ported)"] fn test_require_types() { todo!() } +#[ignore = "JsonSchema Validator::get_errors returns Vec<String>; cannot represent structured property/message/constraint error arrays asserted here, and Validator::validate is not ported"] #[test] -#[ignore = "needs JsonFile schema validation against the bundled composer-schema.json (not ported)"] fn test_minimum_stability_values() { todo!() } diff --git a/crates/shirabe/tests/json/json_file_test.rs b/crates/shirabe/tests/json/json_file_test.rs index b6fecc6..0224ae6 100644 --- a/crates/shirabe/tests/json/json_file_test.rs +++ b/crates/shirabe/tests/json/json_file_test.rs @@ -1,6 +1,9 @@ //! ref: composer/tests/Composer/Test/Json/JsonFileTest.php -use shirabe::json::JsonFile; +use indexmap::IndexMap; +use shirabe::json::{JsonEncodeOptions, JsonFile, JsonValidationException}; +use shirabe_external_packages::seld::json_lint::ParsingException; +use shirabe_php_shim::PhpMixed; /// ref: JsonFileTest::expectParseException fn expect_parse_exception(text: &str, json: &str) { @@ -9,6 +12,47 @@ fn expect_parse_exception(text: &str, json: &str) { assert!(message.contains(text), "expected {text:?} in {message:?}"); } +/// ref: JsonFileTest::assertJsonFormat +fn assert_json_format(json: &str, data: &PhpMixed, options: Option<JsonEncodeOptions>) { + let json = json.replace('\r', ""); + match options { + None => assert_eq!(json, JsonFile::encode(data)), + Some(options) => assert_eq!(json, JsonFile::encode_with_options(data, options)), + } +} + +/// PHP's JSON_UNESCAPED_UNICODE-only flag set (used by testOnlyUnicode). +fn unescaped_unicode_only() -> JsonEncodeOptions { + JsonEncodeOptions { + unescaped_slashes: false, + pretty_print: false, + unescaped_unicode: true, + indent: JsonFile::INDENT_DEFAULT.to_string(), + } +} + +fn fixture_path(name: &str) -> std::path::PathBuf { + std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR")) + .join("../../composer/tests/Composer/Test/Json/Fixtures") + .join(name) +} + +/// ref: TestCase::createTempFile (PHP tempnam()) +fn create_temp_file() -> String { + let mut path = std::env::temp_dir(); + let unique = format!( + "shirabe_jsonfiletest_{}_{}", + std::process::id(), + std::time::SystemTime::now() + .duration_since(std::time::UNIX_EPOCH) + .unwrap() + .as_nanos() + ); + path.push(unique); + std::fs::write(&path, b"").unwrap(); + path.to_str().unwrap().to_string() +} + #[test] #[ignore = "JsonFile::parse_json error path reaches JsonParser::new()/lint() (todo!()); the JSON linter is not yet ported"] fn test_parse_error_detect_extra_comma() { @@ -79,153 +123,459 @@ fn test_parse_error_detect_missing_colon() { expect_parse_exception("Parse error on line 3", json); } -// The encode cases assert JsonFile::encode output for specific PHP flag combinations and -// data shapes (incl. stdClass vs array). Faithful flag mapping and value modeling are not -// reproduced here. #[test] -#[ignore = "asserts JsonFile::encode output for specific PHP flag/value combinations; not reproduced here"] +#[ignore] fn test_simple_json_string() { - todo!() + let mut data: IndexMap<String, PhpMixed> = IndexMap::new(); + data.insert( + "name".to_string(), + PhpMixed::String("composer/composer".to_string()), + ); + let json = "{\n \"name\": \"composer/composer\"\n}"; + assert_json_format(json, &PhpMixed::Array(data), None); } #[test] -#[ignore = "asserts JsonFile::encode output for specific PHP flag/value combinations; not reproduced here"] +#[ignore] fn test_trailing_backslash() { - todo!() + let mut data: IndexMap<String, PhpMixed> = IndexMap::new(); + data.insert( + "Metadata\\".to_string(), + PhpMixed::String("src/".to_string()), + ); + let json = "{\n \"Metadata\\\\\": \"src/\"\n}"; + assert_json_format(json, &PhpMixed::Array(data), None); } #[test] -#[ignore = "asserts JsonFile::encode output for specific PHP flag/value combinations; not reproduced here"] +#[ignore] fn test_format_empty_array() { - todo!() + let mut data: IndexMap<String, PhpMixed> = IndexMap::new(); + data.insert("test".to_string(), PhpMixed::List(vec![])); + data.insert("test2".to_string(), PhpMixed::Object(IndexMap::new())); + let json = "{\n \"test\": [],\n \"test2\": {}\n}"; + assert_json_format(json, &PhpMixed::Array(data), None); } #[test] -#[ignore = "asserts JsonFile::encode output for specific PHP flag/value combinations; not reproduced here"] +#[ignore] fn test_escape() { - todo!() + let mut data: IndexMap<String, PhpMixed> = IndexMap::new(); + data.insert( + "Metadata\\\"".to_string(), + PhpMixed::String("src/".to_string()), + ); + let json = "{\n \"Metadata\\\\\\\"\": \"src/\"\n}"; + assert_json_format(json, &PhpMixed::Array(data), None); } #[test] -#[ignore = "asserts JsonFile::encode output for specific PHP flag/value combinations; not reproduced here"] +#[ignore] fn test_unicode() { - todo!() + let mut data: IndexMap<String, PhpMixed> = IndexMap::new(); + data.insert( + "Žluťoučký \" kůň".to_string(), + PhpMixed::String("úpěl ďábelské ódy za €".to_string()), + ); + let json = "{\n \"Žluťoučký \\\" kůň\": \"úpěl ďábelské ódy za €\"\n}"; + assert_json_format(json, &PhpMixed::Array(data), None); } #[test] -#[ignore = "asserts JsonFile::encode output for specific PHP flag/value combinations; not reproduced here"] +#[ignore] fn test_only_unicode() { - todo!() + let data = PhpMixed::String("\\/ƌ".to_string()); + assert_json_format("\"\\\\\\/ƌ\"", &data, Some(unescaped_unicode_only())); } #[test] -#[ignore = "asserts JsonFile::encode output for specific PHP flag/value combinations; not reproduced here"] +#[ignore] fn test_escaped_slashes() { - todo!() + let data = PhpMixed::String("\\/foo".to_string()); + assert_json_format("\"\\\\\\/foo\"", &data, Some(JsonEncodeOptions::none())); } #[test] -#[ignore = "asserts JsonFile::encode output for specific PHP flag/value combinations; not reproduced here"] +#[ignore] fn test_escaped_backslashes() { - todo!() + let data = PhpMixed::String("a\\b".to_string()); + assert_json_format("\"a\\\\b\"", &data, Some(JsonEncodeOptions::none())); } #[test] -#[ignore = "asserts JsonFile::encode output for specific PHP flag/value combinations; not reproduced here"] +#[ignore] fn test_escaped_unicode() { - todo!() + let data = PhpMixed::String("ƌ".to_string()); + assert_json_format("\"\\u018c\"", &data, Some(JsonEncodeOptions::none())); } #[test] -#[ignore = "asserts JsonFile::encode output for specific PHP flag/value combinations; not reproduced here"] +#[ignore] fn test_double_escaped_unicode() { - todo!() + let data = PhpMixed::List(vec![ + PhpMixed::String("Zdjęcia".to_string()), + PhpMixed::String("hjkjhl\\u0119kkjk".to_string()), + ]); + let encoded_data = JsonFile::encode(&data); + + let mut wrapper: IndexMap<String, PhpMixed> = IndexMap::new(); + wrapper.insert("t".to_string(), PhpMixed::String(encoded_data)); + let double_encoded_data = JsonFile::encode(&PhpMixed::Array(wrapper)); + + let decoded_data = shirabe_php_shim::json_decode(&double_encoded_data, true).unwrap(); + let t = decoded_data.as_array().unwrap().get("t").unwrap(); + let double_data = shirabe_php_shim::json_decode(t.as_string().unwrap(), true).unwrap(); + assert_eq!(data, double_data); } -// These read a fixture composer.json and assert read/write indentation behaviour. #[test] -#[ignore = "reads a fixture file and asserts indentation behaviour of JsonFile read/write"] +#[ignore] fn test_preserve_indentation_after_read() { - todo!() + let src = fixture_path("tabs.json"); + let dst = fixture_path("tabs2.json"); + std::fs::copy(&src, &dst).unwrap(); + + let mut json_file = JsonFile::new(dst.to_str().unwrap().to_string(), None, None).unwrap(); + let _data = json_file.read().unwrap(); + let mut hash: IndexMap<String, PhpMixed> = IndexMap::new(); + hash.insert("foo".to_string(), PhpMixed::String("baz".to_string())); + json_file.write(PhpMixed::Array(hash)).unwrap(); + + assert_eq!( + "{\n\t\"foo\": \"baz\"\n}\n", + std::fs::read_to_string(&dst).unwrap() + ); + + std::fs::remove_file(&dst).unwrap(); } #[test] -#[ignore = "reads a fixture file and asserts indentation behaviour of JsonFile read/write"] +#[ignore] fn test_overwrites_indentation_by_default() { - todo!() + let src = fixture_path("tabs.json"); + let dst = fixture_path("tabs2.json"); + std::fs::copy(&src, &dst).unwrap(); + + let json_file = JsonFile::new(dst.to_str().unwrap().to_string(), None, None).unwrap(); + let mut hash: IndexMap<String, PhpMixed> = IndexMap::new(); + hash.insert("foo".to_string(), PhpMixed::String("baz".to_string())); + json_file.write(PhpMixed::Array(hash)).unwrap(); + + assert_eq!( + "{\n \"foo\": \"baz\"\n}\n", + std::fs::read_to_string(&dst).unwrap() + ); + + std::fs::remove_file(&dst).unwrap(); } -// validateSchema needs the bundled JSON schema and the json-schema validator plus fixtures. #[test] -#[ignore = "needs JsonFile::validateSchema (json-schema validation) and the schema/fixtures"] +#[ignore] fn test_schema_validation() { - todo!() + let path = fixture_path("composer.json"); + let json = JsonFile::new(path.to_str().unwrap().to_string(), None, None).unwrap(); + json.validate_schema(JsonFile::STRICT_SCHEMA, None).unwrap(); + json.validate_schema(JsonFile::LAX_SCHEMA, None).unwrap(); } #[test] -#[ignore = "needs JsonFile::validateSchema (json-schema validation) and the schema/fixtures"] +#[ignore] fn test_schema_validation_error() { - todo!() + let file = create_temp_file(); + std::fs::write(&file, b"{ \"name\": null }").unwrap(); + let json = JsonFile::new(file.clone(), None, None).unwrap(); + let expected_message = format!("\"{}\" does not match the expected JSON schema", file); + let expected_error = "name : NULL value found, but a string is required".to_string(); + + let err = json + .validate_schema(JsonFile::STRICT_SCHEMA, None) + .unwrap_err(); + let e = err.downcast_ref::<JsonValidationException>().unwrap(); + assert_eq!(expected_message, e.get_message()); + assert!(e.get_errors().contains(&expected_error)); + + let err = json + .validate_schema(JsonFile::LAX_SCHEMA, None) + .unwrap_err(); + let e = err.downcast_ref::<JsonValidationException>().unwrap(); + assert_eq!(expected_message, e.get_message()); + assert!(e.get_errors().contains(&expected_error)); + + std::fs::remove_file(&file).unwrap(); } #[test] -#[ignore = "needs JsonFile::validateSchema (json-schema validation) and the schema/fixtures"] +#[ignore] fn test_schema_validation_lax_additional_properties() { - todo!() + let file = create_temp_file(); + std::fs::write( + &file, + b"{ \"name\": \"vendor/package\", \"description\": \"generic description\", \"foo\": \"bar\" }", + ) + .unwrap(); + let json = JsonFile::new(file.clone(), None, None).unwrap(); + + let err = json + .validate_schema(JsonFile::STRICT_SCHEMA, None) + .unwrap_err(); + let e = err.downcast_ref::<JsonValidationException>().unwrap(); + assert_eq!( + format!("\"{}\" does not match the expected JSON schema", file), + e.get_message() + ); + assert_eq!( + &vec![ + "The property foo is not defined and the definition does not allow additional properties" + .to_string() + ], + e.get_errors() + ); + + json.validate_schema(JsonFile::LAX_SCHEMA, None).unwrap(); + std::fs::remove_file(&file).unwrap(); } #[test] -#[ignore = "needs JsonFile::validateSchema (json-schema validation) and the schema/fixtures"] +#[ignore] fn test_schema_validation_lax_required() { - todo!() + let file = create_temp_file(); + let json = JsonFile::new(file.clone(), None, None).unwrap(); + + let expected_message = format!("\"{}\" does not match the expected JSON schema", file); + + std::fs::write(&file, b"{ }").unwrap(); + let err = json + .validate_schema(JsonFile::STRICT_SCHEMA, None) + .unwrap_err(); + let e = err.downcast_ref::<JsonValidationException>().unwrap(); + assert_eq!(expected_message, e.get_message()); + let errors = e.get_errors(); + assert!(errors.contains(&"name : The property name is required".to_string())); + assert!(errors.contains(&"description : The property description is required".to_string())); + json.validate_schema(JsonFile::LAX_SCHEMA, None).unwrap(); + + std::fs::write(&file, b"{ \"name\": \"vendor/package\" }").unwrap(); + let err = json + .validate_schema(JsonFile::STRICT_SCHEMA, None) + .unwrap_err(); + let e = err.downcast_ref::<JsonValidationException>().unwrap(); + assert_eq!(expected_message, e.get_message()); + assert_eq!( + &vec!["description : The property description is required".to_string()], + e.get_errors() + ); + json.validate_schema(JsonFile::LAX_SCHEMA, None).unwrap(); + + std::fs::write(&file, b"{ \"description\": \"generic description\" }").unwrap(); + let err = json + .validate_schema(JsonFile::STRICT_SCHEMA, None) + .unwrap_err(); + let e = err.downcast_ref::<JsonValidationException>().unwrap(); + assert_eq!(expected_message, e.get_message()); + assert_eq!( + &vec!["name : The property name is required".to_string()], + e.get_errors() + ); + json.validate_schema(JsonFile::LAX_SCHEMA, None).unwrap(); + + std::fs::write(&file, b"{ \"type\": \"library\" }").unwrap(); + let err = json + .validate_schema(JsonFile::STRICT_SCHEMA, None) + .unwrap_err(); + let e = err.downcast_ref::<JsonValidationException>().unwrap(); + assert_eq!(expected_message, e.get_message()); + let errors = e.get_errors(); + assert!(errors.contains(&"name : The property name is required".to_string())); + assert!(errors.contains(&"description : The property description is required".to_string())); + json.validate_schema(JsonFile::LAX_SCHEMA, None).unwrap(); + + std::fs::write(&file, b"{ \"type\": \"project\" }").unwrap(); + let err = json + .validate_schema(JsonFile::STRICT_SCHEMA, None) + .unwrap_err(); + let e = err.downcast_ref::<JsonValidationException>().unwrap(); + assert_eq!(expected_message, e.get_message()); + let errors = e.get_errors(); + assert!(errors.contains(&"name : The property name is required".to_string())); + assert!(errors.contains(&"description : The property description is required".to_string())); + json.validate_schema(JsonFile::LAX_SCHEMA, None).unwrap(); + + std::fs::write( + &file, + b"{ \"name\": \"vendor/package\", \"description\": \"generic description\" }", + ) + .unwrap(); + json.validate_schema(JsonFile::STRICT_SCHEMA, None).unwrap(); + json.validate_schema(JsonFile::LAX_SCHEMA, None).unwrap(); + + std::fs::remove_file(&file).unwrap(); } #[test] -#[ignore = "needs JsonFile::validateSchema (json-schema validation) and the schema/fixtures"] +#[ignore] fn test_custom_schema_validation_lax() { - todo!() + let file = create_temp_file(); + std::fs::write( + &file, + b"{ \"custom\": \"property\", \"another custom\": \"property\" }", + ) + .unwrap(); + + let schema = create_temp_file(); + std::fs::write( + &schema, + b"{ \"properties\": { \"custom\": { \"type\": \"string\" }}}", + ) + .unwrap(); + + let json = JsonFile::new(file.clone(), None, None).unwrap(); + + json.validate_schema(JsonFile::LAX_SCHEMA, Some(&schema)) + .unwrap(); + + std::fs::remove_file(&file).unwrap(); + std::fs::remove_file(&schema).unwrap(); } #[test] -#[ignore = "needs JsonFile::validateSchema (json-schema validation) and the schema/fixtures"] +#[ignore] fn test_custom_schema_validation_strict() { - todo!() + let file = create_temp_file(); + std::fs::write(&file, b"{ \"custom\": \"property\" }").unwrap(); + + let schema = create_temp_file(); + std::fs::write( + &schema, + b"{ \"properties\": { \"custom\": { \"type\": \"string\" }}}", + ) + .unwrap(); + + let json = JsonFile::new(file.clone(), None, None).unwrap(); + + json.validate_schema(JsonFile::STRICT_SCHEMA, Some(&schema)) + .unwrap(); + + std::fs::remove_file(&file).unwrap(); + std::fs::remove_file(&schema).unwrap(); } #[test] -#[ignore = "needs JsonFile::validateSchema (json-schema validation) and the schema/fixtures"] +#[ignore] fn test_auth_schema_validation_with_custom_data_source() { - todo!() + let json = shirabe_php_shim::json_decode("{\"github-oauth\": \"foo\"}", false).unwrap(); + let expected_message = "\"COMPOSER_AUTH\" does not match the expected JSON schema".to_string(); + let expected_error = "github-oauth : String value found, but an object is required".to_string(); + + let err = JsonFile::validate_json_schema("COMPOSER_AUTH", &json, JsonFile::AUTH_SCHEMA, None) + .unwrap_err(); + let e = err.downcast_ref::<JsonValidationException>().unwrap(); + assert_eq!(expected_message, e.get_message()); + assert_eq!(&vec![expected_error], e.get_errors()); +} + +/// ref: shared `$data` for the simple merge-conflict cases. +fn merge_conflict_simple_data() -> PhpMixed { + let mut data: IndexMap<String, PhpMixed> = IndexMap::new(); + data.insert( + "_readme".to_string(), + PhpMixed::List(vec![ + PhpMixed::String( + "This file locks the dependencies of your project to a known state".to_string(), + ), + PhpMixed::String( + "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies" + .to_string(), + ), + PhpMixed::String("This file is @generated automatically".to_string()), + ]), + ); + data.insert( + "content-hash".to_string(), + PhpMixed::String( + "VCS merge conflict detected. Please run `composer update --lock`.".to_string(), + ), + ); + data.insert("packages".to_string(), PhpMixed::List(vec![])); + data.insert("packages-dev".to_string(), PhpMixed::List(vec![])); + data.insert("aliases".to_string(), PhpMixed::List(vec![])); + data.insert( + "minimum-stability".to_string(), + PhpMixed::String("stable".to_string()), + ); + data.insert("stability-flags".to_string(), PhpMixed::List(vec![])); + data.insert("prefer-stable".to_string(), PhpMixed::Bool(false)); + data.insert("prefer-lowest".to_string(), PhpMixed::Bool(false)); + data.insert("platform".to_string(), PhpMixed::List(vec![])); + data.insert("platform-dev".to_string(), PhpMixed::List(vec![])); + data.insert( + "plugin-api-version".to_string(), + PhpMixed::String("2.3.0".to_string()), + ); + PhpMixed::Array(data) } -// The merge-conflict cases build large lock-file structures (with VCS conflict markers) and -// assert the resulting ParsingException; the fixtures are sizeable and not reproduced here. #[test] -#[ignore = "builds a large lock-file structure with VCS merge markers; not reproduced here"] +#[ignore] fn test_composer_lock_file_merge_conflict_simple() { - todo!() + let data = merge_conflict_simple_data(); + let json = + std::fs::read_to_string(fixture_path("composer-lock-merge-conflict-simple.txt")).unwrap(); + assert_eq!( + data, + JsonFile::parse_json(Some(&json), Some("/path/to/composer.lock")).unwrap() + ); } #[test] -#[ignore = "builds a large lock-file structure with VCS merge markers; not reproduced here"] +#[ignore] fn test_composer_lock_file_merge_conflict_simple_crlf() { - todo!() + let data = merge_conflict_simple_data(); + let json = + std::fs::read_to_string(fixture_path("composer-lock-merge-conflict-simple.txt")).unwrap(); + assert_eq!( + data, + JsonFile::parse_json(Some(&json), Some("/path/to/composer.lock")).unwrap() + ); } #[test] -#[ignore = "builds a large lock-file structure with VCS merge markers; not reproduced here"] +#[ignore] fn test_composer_lock_file_merge_conflict_complex() { - todo!() + // complex files have multiple conflict markers and can thus not be simply resolved + let data = + std::fs::read_to_string(fixture_path("composer-lock-merge-conflict-complex.txt")).unwrap(); + + let err = JsonFile::parse_json(Some(&data), Some("/path/to/composer.lock")).unwrap_err(); + assert!(err.downcast_ref::<ParsingException>().is_some()); } #[test] -#[ignore = "builds a large lock-file structure with VCS merge markers; not reproduced here"] +#[ignore] fn test_composer_lock_file_merge_conflict_complex_crlf() { - todo!() + // complex files have multiple conflict markers and can thus not be simply resolved + let data = std::fs::read_to_string(fixture_path( + "composer-lock-merge-conflict-complex-crlf.txt", + )) + .unwrap(); + + let err = JsonFile::parse_json(Some(&data), Some("/path/to/composer.lock")).unwrap_err(); + assert!(err.downcast_ref::<ParsingException>().is_some()); } #[test] -#[ignore = "builds a large lock-file structure with VCS merge markers; not reproduced here"] +#[ignore] fn test_composer_lock_file_merge_conflict_extended() { - todo!() + let data = + std::fs::read_to_string(fixture_path("composer-lock-merge-conflict-extended.txt")).unwrap(); + + let json = JsonFile::parse_json(Some(&data), Some("/path/to/composer.lock")).unwrap(); + assert_eq!( + "VCS merge conflict detected. Please run `composer update --lock`.", + json.as_array() + .unwrap() + .get("content-hash") + .unwrap() + .as_string() + .unwrap() + ); } diff --git a/crates/shirabe/tests/json/json_formatter_test.rs b/crates/shirabe/tests/json/json_formatter_test.rs index 328c942..b27f25b 100644 --- a/crates/shirabe/tests/json/json_formatter_test.rs +++ b/crates/shirabe/tests/json/json_formatter_test.rs @@ -2,16 +2,16 @@ /// Test if ę will get correctly formatted (unescaped) /// https://github.com/composer/composer/issues/2613 +#[ignore = "JsonFormatter is not implemented in crates/shirabe/src/json"] #[test] -#[ignore = "Composer\\Json\\JsonFormatter (json/json_formatter.rs) is not yet ported"] fn test_unicode_with_prepended_slash() { todo!() } /// Surrogate pairs are intentionally skipped and not unescaped /// https://github.com/composer/composer/issues/7510 +#[ignore = "JsonFormatter is not implemented in crates/shirabe/src/json"] #[test] -#[ignore = "Composer\\Json\\JsonFormatter (json/json_formatter.rs) is not yet ported"] fn test_utf16_surrogate_pair() { todo!() } diff --git a/crates/shirabe/tests/json/json_manipulator_test.rs b/crates/shirabe/tests/json/json_manipulator_test.rs index 7cf8e87..96b74f7 100644 --- a/crates/shirabe/tests/json/json_manipulator_test.rs +++ b/crates/shirabe/tests/json/json_manipulator_test.rs @@ -1,301 +1,5585 @@ //! ref: composer/tests/Composer/Test/Json/JsonManipulatorTest.php +use indexmap::IndexMap; +use shirabe::json::{JsonFile, JsonManipulator}; +use shirabe_php_shim::PhpMixed; + +fn s(v: &str) -> PhpMixed { + PhpMixed::String(v.to_string()) +} + +fn arr(pairs: &[(&str, PhpMixed)]) -> PhpMixed { + let mut m: IndexMap<String, PhpMixed> = IndexMap::new(); + for (k, v) in pairs { + m.insert(k.to_string(), v.clone()); + } + PhpMixed::Array(m) +} + #[test] -#[ignore = "test body not yet ported (todo!() stub)"] +#[ignore] fn test_add_link() { - todo!() + let cases: Vec<(&str, &str, &str, &str, &str)> = vec![ + ( + r#"{}"#, + r#"require"#, + r#"vendor/baz"#, + r#"qux"#, + r#"{ + "require": { + "vendor/baz": "qux" + } +} +"#, + ), + ( + r#"{ + "foo": "bar" +}"#, + r#"require"#, + r#"vendor/baz"#, + r#"qux"#, + r#"{ + "foo": "bar", + "require": { + "vendor/baz": "qux" + } +} +"#, + ), + ( + r#"{ + "require": { + } +}"#, + r#"require"#, + r#"vendor/baz"#, + r#"qux"#, + r#"{ + "require": { + "vendor/baz": "qux" + } +} +"#, + ), + ( + r#"{ + "empty": "", + "require": { + "foo": "bar" + } +}"#, + r#"require"#, + r#"vendor/baz"#, + r#"qux"#, + r#"{ + "empty": "", + "require": { + "foo": "bar", + "vendor/baz": "qux" + } +} +"#, + ), + ( + r#"{ + "require": + { + "foo": "bar", + "vendor/baz": "baz" + } +}"#, + r#"require"#, + r#"vendor/baz"#, + r#"qux"#, + r#"{ + "require": + { + "foo": "bar", + "vendor/baz": "qux" + } +} +"#, + ), + ( + r#"{ + "require": + { + "foo": "bar", + "vendor/baz": "baz" + } +}"#, + r#"require"#, + r#"vEnDoR/bAz"#, + r#"qux"#, + r#"{ + "require": + { + "foo": "bar", + "vendor/baz": "qux" + } +} +"#, + ), + ( + r#"{ + "require": + { + "foo": "bar", + "vendor\/baz": "baz" + } +}"#, + r#"require"#, + r#"vendor/baz"#, + r#"qux"#, + r#"{ + "require": + { + "foo": "bar", + "vendor/baz": "qux" + } +} +"#, + ), + ( + r#"{ + "require": + { + "foo": "bar", + "vendor\/baz": "baz" + } +}"#, + r#"require"#, + r#"vEnDoR/bAz"#, + r#"qux"#, + r#"{ + "require": + { + "foo": "bar", + "vendor/baz": "qux" + } +} +"#, + ), + ( + r#"{ + "require": { + "foo": "bar" + }, + "repositories": [{ + "type": "package", + "package": { + "require": { + "foo": "bar" + } + } + }] +}"#, + r#"require"#, + r#"foo"#, + r#"qux"#, + r#"{ + "require": { + "foo": "qux" + }, + "repositories": [{ + "type": "package", + "package": { + "require": { + "foo": "bar" + } + } + }] +} +"#, + ), + ( + r#"{ + "repositories": [{ + "type": "package", + "package": { + "require": { + "foo": "bar" + } + } + }] +}"#, + r#"require"#, + r#"foo"#, + r#"qux"#, + r#"{ + "repositories": [{ + "type": "package", + "package": { + "require": { + "foo": "bar" + } + } + }], + "require": { + "foo": "qux" + } +} +"#, + ), + ( + r#"{ + "require": { + "php": "5.*" + } +}"#, + r#"require-dev"#, + r#"foo"#, + r#"qux"#, + r#"{ + "require": { + "php": "5.*" + }, + "require-dev": { + "foo": "qux" + } +} +"#, + ), + ( + r#"{ + "require": { + "php": "5.*" + }, + "require-dev": { + "foo": "bar" + } +}"#, + r#"require-dev"#, + r#"foo"#, + r#"qux"#, + r#"{ + "require": { + "php": "5.*" + }, + "require-dev": { + "foo": "qux" + } +} +"#, + ), + ( + r#"{ + "repositories": [{ + "type": "package", + "package": { + "bar": "ba[z", + "dist": { + "url": "http...", + "type": "zip" + }, + "autoload": { + "classmap": [ "foo/bar" ] + } + } + }], + "require": { + "php": "5.*" + }, + "require-dev": { + "foo": "bar" + } +}"#, + r#"require-dev"#, + r#"foo"#, + r#"qux"#, + r#"{ + "repositories": [{ + "type": "package", + "package": { + "bar": "ba[z", + "dist": { + "url": "http...", + "type": "zip" + }, + "autoload": { + "classmap": [ "foo/bar" ] + } + } + }], + "require": { + "php": "5.*" + }, + "require-dev": { + "foo": "qux" + } +} +"#, + ), + ( + r#"{ + "config": { + "cache-files-ttl": 0, + "discard-changes": true + }, + "minimum-stability": "stable", + "prefer-stable": false, + "provide": { + "heroku-sys/cedar": "14.2016.03.22" + }, + "repositories": [ + { + "packagist.org": false + }, + { + "type": "package", + "package": [ + { + "type": "metapackage", + "name": "anthonymartin/geo-location", + "version": "v1.0.0", + "require": { + "heroku-sys/php": ">=5.3.0" + }, + "replace": [], + "provide": [], + "conflict": [] + }, + { + "type": "metapackage", + "name": "aws/aws-sdk-php", + "version": "3.9.4", + "require": { + "heroku-sys/php": ">=5.5" + }, + "replace": [], + "provide": [], + "conflict": [] + }, + { + "type": "metapackage", + "name": "cloudinary/cloudinary_php", + "version": "dev-master", + "require": { + "heroku-sys/ext-curl": "*", + "heroku-sys/ext-json": "*", + "heroku-sys/php": ">=5.3.0" + }, + "replace": [], + "provide": [], + "conflict": [] + }, + { + "type": "metapackage", + "name": "doctrine/annotations", + "version": "v1.2.7", + "require": { + "heroku-sys/php": ">=5.3.2" + }, + "replace": [], + "provide": [], + "conflict": [] + }, + { + "type": "metapackage", + "name": "doctrine/cache", + "version": "v1.6.0", + "require": { + "heroku-sys/php": "~5.5|~7.0" + }, + "replace": [], + "provide": [], + "conflict": [] + }, + { + "type": "metapackage", + "name": "doctrine/collections", + "version": "v1.3.0", + "require": { + "heroku-sys/php": ">=5.3.2" + }, + "replace": [], + "provide": [], + "conflict": [] + }, + { + "type": "metapackage", + "name": "doctrine/common", + "version": "v2.6.1", + "require": { + "heroku-sys/php": "~5.5|~7.0" + }, + "replace": [], + "provide": [], + "conflict": [] + }, + { + "type": "metapackage", + "name": "doctrine/inflector", + "version": "v1.1.0", + "require": { + "heroku-sys/php": ">=5.3.2" + }, + "replace": [], + "provide": [], + "conflict": [] + }, + { + "type": "metapackage", + "name": "doctrine/lexer", + "version": "v1.0.1", + "require": { + "heroku-sys/php": ">=5.3.2" + }, + "replace": [], + "provide": [], + "conflict": [] + }, + { + "type": "metapackage", + "name": "geoip/geoip", + "version": "v1.16", + "require": [], + "replace": [], + "provide": [], + "conflict": { + "heroku-sys/ext-geoip": "*" + } + }, + { + "type": "metapackage", + "name": "giggsey/libphonenumber-for-php", + "version": "7.2.5", + "require": { + "heroku-sys/ext-mbstring": "*" + }, + "replace": [], + "provide": [], + "conflict": [] + }, + { + "type": "metapackage", + "name": "guzzlehttp/guzzle", + "version": "5.3.0", + "require": { + "heroku-sys/php": ">=5.4.0" + }, + "replace": [], + "provide": [], + "conflict": [] + }, + { + "type": "metapackage", + "name": "guzzlehttp/promises", + "version": "1.0.3", + "require": { + "heroku-sys/php": ">=5.5.0" + }, + "replace": [], + "provide": [], + "conflict": [] + }, + { + "type": "metapackage", + "name": "guzzlehttp/psr7", + "version": "1.2.3", + "require": { + "heroku-sys/php": ">=5.4.0" + }, + "replace": [], + "provide": [], + "conflict": [] + }, + { + "type": "metapackage", + "name": "guzzlehttp/ringphp", + "version": "1.1.0", + "require": { + "heroku-sys/php": ">=5.4.0" + }, + "replace": [], + "provide": [], + "conflict": [] + }, + { + "type": "metapackage", + "name": "guzzlehttp/streams", + "version": "3.0.0", + "require": { + "heroku-sys/php": ">=5.4.0" + }, + "replace": [], + "provide": [], + "conflict": [] + }, + { + "type": "metapackage", + "name": "hipchat/hipchat-php", + "version": "v1.4", + "require": { + "heroku-sys/php": ">=5.3.0" + }, + "replace": [], + "provide": [], + "conflict": [] + }, + { + "type": "metapackage", + "name": "kriswallsmith/buzz", + "version": "v0.15", + "require": { + "heroku-sys/php": ">=5.3.0" + }, + "replace": [], + "provide": [], + "conflict": [] + }, + { + "type": "metapackage", + "name": "league/csv", + "version": "8.0.0", + "require": { + "heroku-sys/ext-mbstring": "*", + "heroku-sys/php": ">=5.5.0" + }, + "replace": [], + "provide": [], + "conflict": [] + }, + { + "type": "metapackage", + "name": "league/fractal", + "version": "0.13.0", + "require": { + "heroku-sys/php": ">=5.4" + }, + "replace": [], + "provide": [], + "conflict": [] + }, + { + "type": "metapackage", + "name": "mashape/unirest-php", + "version": "1.2.1", + "require": { + "heroku-sys/ext-curl": "*", + "heroku-sys/ext-json": "*", + "heroku-sys/php": ">=5.3.0" + }, + "replace": [], + "provide": [], + "conflict": [] + }, + { + "type": "metapackage", + "name": "mtdowling/jmespath.php", + "version": "2.3.0", + "require": { + "heroku-sys/php": ">=5.4.0" + }, + "replace": [], + "provide": [], + "conflict": [] + }, + { + "type": "metapackage", + "name": "palex/phpstructureddata", + "version": "v2.0.1", + "require": { + "heroku-sys/php": ">=5.3.0" + }, + "replace": [], + "provide": [], + "conflict": [] + }, + { + "type": "metapackage", + "name": "psr/http-message", + "version": "1.0", + "require": { + "heroku-sys/php": ">=5.3.0" + }, + "replace": [], + "provide": [], + "conflict": [] + }, + { + "type": "metapackage", + "name": "react/promise", + "version": "v2.2.1", + "require": { + "heroku-sys/php": ">=5.4.0" + }, + "replace": [], + "provide": [], + "conflict": [] + }, + { + "type": "metapackage", + "name": "rollbar/rollbar", + "version": "v0.15.0", + "require": { + "heroku-sys/ext-curl": "*" + }, + "replace": [], + "provide": [], + "conflict": [] + }, + { + "type": "metapackage", + "name": "ronanguilloux/isocodes", + "version": "1.2.0", + "require": { + "heroku-sys/ext-bcmath": "*", + "heroku-sys/php": ">=5.4.0" + }, + "replace": [], + "provide": [], + "conflict": [] + }, + { + "type": "metapackage", + "name": "sendgrid/sendgrid", + "version": "2.1.1", + "require": { + "heroku-sys/php": ">=5.3" + }, + "replace": [], + "provide": [], + "conflict": [] + }, + { + "type": "metapackage", + "name": "sendgrid/smtpapi", + "version": "0.0.1", + "require": { + "heroku-sys/php": ">=5.3" + }, + "replace": [], + "provide": [], + "conflict": [] + }, + { + "type": "metapackage", + "name": "symfony/css-selector", + "version": "v2.8.2", + "require": { + "heroku-sys/php": ">=5.3.9" + }, + "replace": [], + "provide": [], + "conflict": [] + }, + { + "type": "metapackage", + "name": "symfony/http-foundation", + "version": "v2.8.2", + "require": { + "heroku-sys/php": ">=5.3.9" + }, + "replace": [], + "provide": [], + "conflict": [] + }, + { + "type": "metapackage", + "name": "symfony/polyfill-php54", + "version": "v1.1.0", + "require": { + "heroku-sys/php": ">=5.3.3" + }, + "replace": [], + "provide": [], + "conflict": [] + }, + { + "type": "metapackage", + "name": "symfony/polyfill-php55", + "version": "v1.1.0", + "require": { + "heroku-sys/php": ">=5.3.3" + }, + "replace": [], + "provide": [], + "conflict": [] + }, + { + "type": "metapackage", + "name": "thepixeldeveloper/sitemap", + "version": "3.0.0", + "require": { + "heroku-sys/php": ">=5.3.0" + }, + "replace": [], + "provide": [], + "conflict": [] + }, + { + "type": "metapackage", + "name": "tijsverkoyen/css-to-inline-styles", + "version": "1.5.5", + "require": { + "heroku-sys/php": ">=5.3.0" + }, + "replace": [], + "provide": [], + "conflict": [] + }, + { + "type": "metapackage", + "name": "yiisoft/yii", + "version": "1.1.17", + "require": { + "heroku-sys/php": ">=5.1.0" + }, + "replace": [], + "provide": [], + "conflict": [] + }, + { + "type": "metapackage", + "name": "composer.json/composer.lock", + "version": "dev-597511d6d51b96e4a8afeba2c79982e5", + "require": { + "heroku-sys/php": "~5.6.0", + "heroku-sys/ext-newrelic": "*", + "heroku-sys/ext-gd": "*", + "heroku-sys/ext-redis": "*" + }, + "replace": [], + "provide": [], + "conflict": [] + } + ] + } + ], + "require": { + "composer.json/composer.lock": "dev-597511d6d51b96e4a8afeba2c79982e5", + "anthonymartin/geo-location": "v1.0.0", + "aws/aws-sdk-php": "3.9.4", + "cloudinary/cloudinary_php": "dev-master", + "doctrine/annotations": "v1.2.7", + "doctrine/cache": "v1.6.0", + "doctrine/collections": "v1.3.0", + "doctrine/common": "v2.6.1", + "doctrine/inflector": "v1.1.0", + "doctrine/lexer": "v1.0.1", + "geoip/geoip": "v1.16", + "giggsey/libphonenumber-for-php": "7.2.5", + "guzzlehttp/guzzle": "5.3.0", + "guzzlehttp/promises": "1.0.3", + "guzzlehttp/psr7": "1.2.3", + "guzzlehttp/ringphp": "1.1.0", + "guzzlehttp/streams": "3.0.0", + "hipchat/hipchat-php": "v1.4", + "kriswallsmith/buzz": "v0.15", + "league/csv": "8.0.0", + "league/fractal": "0.13.0", + "mashape/unirest-php": "1.2.1", + "mtdowling/jmespath.php": "2.3.0", + "palex/phpstructureddata": "v2.0.1", + "psr/http-message": "1.0", + "react/promise": "v2.2.1", + "rollbar/rollbar": "v0.15.0", + "ronanguilloux/isocodes": "1.2.0", + "sendgrid/sendgrid": "2.1.1", + "sendgrid/smtpapi": "0.0.1", + "symfony/css-selector": "v2.8.2", + "symfony/http-foundation": "v2.8.2", + "symfony/polyfill-php54": "v1.1.0", + "symfony/polyfill-php55": "v1.1.0", + "thepixeldeveloper/sitemap": "3.0.0", + "tijsverkoyen/css-to-inline-styles": "1.5.5", + "yiisoft/yii": "1.1.17", + "heroku-sys/apache": "^2.4.10", + "heroku-sys/nginx": "~1.8.0" + } +}"#, + r#"require"#, + r#"foo"#, + r#"qux"#, + r#"{ + "config": { + "cache-files-ttl": 0, + "discard-changes": true + }, + "minimum-stability": "stable", + "prefer-stable": false, + "provide": { + "heroku-sys/cedar": "14.2016.03.22" + }, + "repositories": [ + { + "packagist.org": false + }, + { + "type": "package", + "package": [ + { + "type": "metapackage", + "name": "anthonymartin/geo-location", + "version": "v1.0.0", + "require": { + "heroku-sys/php": ">=5.3.0" + }, + "replace": [], + "provide": [], + "conflict": [] + }, + { + "type": "metapackage", + "name": "aws/aws-sdk-php", + "version": "3.9.4", + "require": { + "heroku-sys/php": ">=5.5" + }, + "replace": [], + "provide": [], + "conflict": [] + }, + { + "type": "metapackage", + "name": "cloudinary/cloudinary_php", + "version": "dev-master", + "require": { + "heroku-sys/ext-curl": "*", + "heroku-sys/ext-json": "*", + "heroku-sys/php": ">=5.3.0" + }, + "replace": [], + "provide": [], + "conflict": [] + }, + { + "type": "metapackage", + "name": "doctrine/annotations", + "version": "v1.2.7", + "require": { + "heroku-sys/php": ">=5.3.2" + }, + "replace": [], + "provide": [], + "conflict": [] + }, + { + "type": "metapackage", + "name": "doctrine/cache", + "version": "v1.6.0", + "require": { + "heroku-sys/php": "~5.5|~7.0" + }, + "replace": [], + "provide": [], + "conflict": [] + }, + { + "type": "metapackage", + "name": "doctrine/collections", + "version": "v1.3.0", + "require": { + "heroku-sys/php": ">=5.3.2" + }, + "replace": [], + "provide": [], + "conflict": [] + }, + { + "type": "metapackage", + "name": "doctrine/common", + "version": "v2.6.1", + "require": { + "heroku-sys/php": "~5.5|~7.0" + }, + "replace": [], + "provide": [], + "conflict": [] + }, + { + "type": "metapackage", + "name": "doctrine/inflector", + "version": "v1.1.0", + "require": { + "heroku-sys/php": ">=5.3.2" + }, + "replace": [], + "provide": [], + "conflict": [] + }, + { + "type": "metapackage", + "name": "doctrine/lexer", + "version": "v1.0.1", + "require": { + "heroku-sys/php": ">=5.3.2" + }, + "replace": [], + "provide": [], + "conflict": [] + }, + { + "type": "metapackage", + "name": "geoip/geoip", + "version": "v1.16", + "require": [], + "replace": [], + "provide": [], + "conflict": { + "heroku-sys/ext-geoip": "*" + } + }, + { + "type": "metapackage", + "name": "giggsey/libphonenumber-for-php", + "version": "7.2.5", + "require": { + "heroku-sys/ext-mbstring": "*" + }, + "replace": [], + "provide": [], + "conflict": [] + }, + { + "type": "metapackage", + "name": "guzzlehttp/guzzle", + "version": "5.3.0", + "require": { + "heroku-sys/php": ">=5.4.0" + }, + "replace": [], + "provide": [], + "conflict": [] + }, + { + "type": "metapackage", + "name": "guzzlehttp/promises", + "version": "1.0.3", + "require": { + "heroku-sys/php": ">=5.5.0" + }, + "replace": [], + "provide": [], + "conflict": [] + }, + { + "type": "metapackage", + "name": "guzzlehttp/psr7", + "version": "1.2.3", + "require": { + "heroku-sys/php": ">=5.4.0" + }, + "replace": [], + "provide": [], + "conflict": [] + }, + { + "type": "metapackage", + "name": "guzzlehttp/ringphp", + "version": "1.1.0", + "require": { + "heroku-sys/php": ">=5.4.0" + }, + "replace": [], + "provide": [], + "conflict": [] + }, + { + "type": "metapackage", + "name": "guzzlehttp/streams", + "version": "3.0.0", + "require": { + "heroku-sys/php": ">=5.4.0" + }, + "replace": [], + "provide": [], + "conflict": [] + }, + { + "type": "metapackage", + "name": "hipchat/hipchat-php", + "version": "v1.4", + "require": { + "heroku-sys/php": ">=5.3.0" + }, + "replace": [], + "provide": [], + "conflict": [] + }, + { + "type": "metapackage", + "name": "kriswallsmith/buzz", + "version": "v0.15", + "require": { + "heroku-sys/php": ">=5.3.0" + }, + "replace": [], + "provide": [], + "conflict": [] + }, + { + "type": "metapackage", + "name": "league/csv", + "version": "8.0.0", + "require": { + "heroku-sys/ext-mbstring": "*", + "heroku-sys/php": ">=5.5.0" + }, + "replace": [], + "provide": [], + "conflict": [] + }, + { + "type": "metapackage", + "name": "league/fractal", + "version": "0.13.0", + "require": { + "heroku-sys/php": ">=5.4" + }, + "replace": [], + "provide": [], + "conflict": [] + }, + { + "type": "metapackage", + "name": "mashape/unirest-php", + "version": "1.2.1", + "require": { + "heroku-sys/ext-curl": "*", + "heroku-sys/ext-json": "*", + "heroku-sys/php": ">=5.3.0" + }, + "replace": [], + "provide": [], + "conflict": [] + }, + { + "type": "metapackage", + "name": "mtdowling/jmespath.php", + "version": "2.3.0", + "require": { + "heroku-sys/php": ">=5.4.0" + }, + "replace": [], + "provide": [], + "conflict": [] + }, + { + "type": "metapackage", + "name": "palex/phpstructureddata", + "version": "v2.0.1", + "require": { + "heroku-sys/php": ">=5.3.0" + }, + "replace": [], + "provide": [], + "conflict": [] + }, + { + "type": "metapackage", + "name": "psr/http-message", + "version": "1.0", + "require": { + "heroku-sys/php": ">=5.3.0" + }, + "replace": [], + "provide": [], + "conflict": [] + }, + { + "type": "metapackage", + "name": "react/promise", + "version": "v2.2.1", + "require": { + "heroku-sys/php": ">=5.4.0" + }, + "replace": [], + "provide": [], + "conflict": [] + }, + { + "type": "metapackage", + "name": "rollbar/rollbar", + "version": "v0.15.0", + "require": { + "heroku-sys/ext-curl": "*" + }, + "replace": [], + "provide": [], + "conflict": [] + }, + { + "type": "metapackage", + "name": "ronanguilloux/isocodes", + "version": "1.2.0", + "require": { + "heroku-sys/ext-bcmath": "*", + "heroku-sys/php": ">=5.4.0" + }, + "replace": [], + "provide": [], + "conflict": [] + }, + { + "type": "metapackage", + "name": "sendgrid/sendgrid", + "version": "2.1.1", + "require": { + "heroku-sys/php": ">=5.3" + }, + "replace": [], + "provide": [], + "conflict": [] + }, + { + "type": "metapackage", + "name": "sendgrid/smtpapi", + "version": "0.0.1", + "require": { + "heroku-sys/php": ">=5.3" + }, + "replace": [], + "provide": [], + "conflict": [] + }, + { + "type": "metapackage", + "name": "symfony/css-selector", + "version": "v2.8.2", + "require": { + "heroku-sys/php": ">=5.3.9" + }, + "replace": [], + "provide": [], + "conflict": [] + }, + { + "type": "metapackage", + "name": "symfony/http-foundation", + "version": "v2.8.2", + "require": { + "heroku-sys/php": ">=5.3.9" + }, + "replace": [], + "provide": [], + "conflict": [] + }, + { + "type": "metapackage", + "name": "symfony/polyfill-php54", + "version": "v1.1.0", + "require": { + "heroku-sys/php": ">=5.3.3" + }, + "replace": [], + "provide": [], + "conflict": [] + }, + { + "type": "metapackage", + "name": "symfony/polyfill-php55", + "version": "v1.1.0", + "require": { + "heroku-sys/php": ">=5.3.3" + }, + "replace": [], + "provide": [], + "conflict": [] + }, + { + "type": "metapackage", + "name": "thepixeldeveloper/sitemap", + "version": "3.0.0", + "require": { + "heroku-sys/php": ">=5.3.0" + }, + "replace": [], + "provide": [], + "conflict": [] + }, + { + "type": "metapackage", + "name": "tijsverkoyen/css-to-inline-styles", + "version": "1.5.5", + "require": { + "heroku-sys/php": ">=5.3.0" + }, + "replace": [], + "provide": [], + "conflict": [] + }, + { + "type": "metapackage", + "name": "yiisoft/yii", + "version": "1.1.17", + "require": { + "heroku-sys/php": ">=5.1.0" + }, + "replace": [], + "provide": [], + "conflict": [] + }, + { + "type": "metapackage", + "name": "composer.json/composer.lock", + "version": "dev-597511d6d51b96e4a8afeba2c79982e5", + "require": { + "heroku-sys/php": "~5.6.0", + "heroku-sys/ext-newrelic": "*", + "heroku-sys/ext-gd": "*", + "heroku-sys/ext-redis": "*" + }, + "replace": [], + "provide": [], + "conflict": [] + } + ] + } + ], + "require": { + "composer.json/composer.lock": "dev-597511d6d51b96e4a8afeba2c79982e5", + "anthonymartin/geo-location": "v1.0.0", + "aws/aws-sdk-php": "3.9.4", + "cloudinary/cloudinary_php": "dev-master", + "doctrine/annotations": "v1.2.7", + "doctrine/cache": "v1.6.0", + "doctrine/collections": "v1.3.0", + "doctrine/common": "v2.6.1", + "doctrine/inflector": "v1.1.0", + "doctrine/lexer": "v1.0.1", + "geoip/geoip": "v1.16", + "giggsey/libphonenumber-for-php": "7.2.5", + "guzzlehttp/guzzle": "5.3.0", + "guzzlehttp/promises": "1.0.3", + "guzzlehttp/psr7": "1.2.3", + "guzzlehttp/ringphp": "1.1.0", + "guzzlehttp/streams": "3.0.0", + "hipchat/hipchat-php": "v1.4", + "kriswallsmith/buzz": "v0.15", + "league/csv": "8.0.0", + "league/fractal": "0.13.0", + "mashape/unirest-php": "1.2.1", + "mtdowling/jmespath.php": "2.3.0", + "palex/phpstructureddata": "v2.0.1", + "psr/http-message": "1.0", + "react/promise": "v2.2.1", + "rollbar/rollbar": "v0.15.0", + "ronanguilloux/isocodes": "1.2.0", + "sendgrid/sendgrid": "2.1.1", + "sendgrid/smtpapi": "0.0.1", + "symfony/css-selector": "v2.8.2", + "symfony/http-foundation": "v2.8.2", + "symfony/polyfill-php54": "v1.1.0", + "symfony/polyfill-php55": "v1.1.0", + "thepixeldeveloper/sitemap": "3.0.0", + "tijsverkoyen/css-to-inline-styles": "1.5.5", + "yiisoft/yii": "1.1.17", + "heroku-sys/apache": "^2.4.10", + "heroku-sys/nginx": "~1.8.0", + "foo": "qux" + } +} +"#, + ), + ]; + for (json, r#type, package, constraint, expected) in cases { + let mut manipulator = JsonManipulator::new(json.to_string()).unwrap(); + assert!( + manipulator + .add_link(r#type, package, constraint, false) + .unwrap() + ); + assert_eq!(expected, manipulator.get_contents()); + } } #[test] -#[ignore = "test body not yet ported (todo!() stub)"] +#[ignore] fn test_add_link_and_sort_packages() { - todo!() + let cases: Vec<(&str, &str, &str, &str, bool, &str)> = vec![ + ( + r#"{ + "require": { + "vendor/baz": "qux" + } +}"#, + r#"require"#, + r#"foo"#, + r#"bar"#, + true, + r#"{ + "require": { + "foo": "bar", + "vendor/baz": "qux" + } +} +"#, + ), + ( + r#"{ + "require": { + "vendor/baz": "qux" + } +}"#, + r#"require"#, + r#"foo"#, + r#"bar"#, + false, + r#"{ + "require": { + "vendor/baz": "qux", + "foo": "bar" + } +} +"#, + ), + ( + r#"{ + "require": { + "foo": "baz", + "ext-10gd": "*", + "ext-2mcrypt": "*", + "lib-foo": "*", + "hhvm": "*", + "php": ">=5.5" + } +}"#, + r#"require"#, + r#"igorw/retry"#, + r#"*"#, + true, + r#"{ + "require": { + "php": ">=5.5", + "hhvm": "*", + "ext-2mcrypt": "*", + "ext-10gd": "*", + "lib-foo": "*", + "foo": "baz", + "igorw/retry": "*" + } +} +"#, + ), + ]; + for (json, r#type, package, constraint, sort_packages, expected) in cases { + let mut manipulator = JsonManipulator::new(json.to_string()).unwrap(); + assert!( + manipulator + .add_link(r#type, package, constraint, sort_packages) + .unwrap() + ); + assert_eq!(expected, manipulator.get_contents()); + } } #[test] -#[ignore = "test body not yet ported (todo!() stub)"] +#[ignore] fn test_remove_sub_node() { - todo!() + let cases: Vec<(&str, &str, bool, Option<&str>)> = vec![ + ( + r#"{ + "repositories": { + "foo": { + "foo": "bar", + "bar": "baz" + }, + "bar": { + "foo": "bar", + "bar": "baz" + } + } +}"#, + r#"foo"#, + true, + Some( + r#"{ + "repositories": { + "bar": { + "foo": "bar", + "bar": "baz" + } + } +} +"#, + ), + ), + ( + r#"{ + "repositories": { + "foo": { + "foo": "bar", + "bar": "baz" + }, + "bar": { + "foo": "bar", + "bar": "baz" + } + } +}"#, + r#"bar"#, + true, + Some( + r#"{ + "repositories": { + "foo": { + "foo": "bar", + "bar": "baz" + } + } +} +"#, + ), + ), + ( + r#"{ + "repositories": { + "foo": { + "foo": "bar", + "bar": "baz" + } + } +}"#, + r#"foo"#, + true, + Some( + r#"{ + "repositories": { + } +} +"#, + ), + ), + ( + r#"{ + "repositories": { + "foo\/bar": { + "bar": "baz" + } + } +}"#, + r#"foo/bar"#, + true, + Some( + r#"{ + "repositories": { + } +} +"#, + ), + ), + ( + r#"{ + "repositories": { + "foo": { + "foo": "bar", + "bar": "baz" + }, + "bar": { + "foo": "bar", + "bar": "baz" + }, + "baz": { + "foo": "bar", + "bar": "baz" + } + } +}"#, + r#"bar"#, + true, + Some( + r#"{ + "repositories": { + "foo": { + "foo": "bar", + "bar": "baz" + }, + "baz": { + "foo": "bar", + "bar": "baz" + } + } +} +"#, + ), + ), + ( + r#"{ + "repositories": { + "main": { + "foo": "bar", + "bar": "baz" + } + } +}"#, + r#"removenotthere"#, + true, + Some( + r#"{ + "repositories": { + "main": { + "foo": "bar", + "bar": "baz" + } + } +} +"#, + ), + ), + ( + r#"{ + "repositories": { + "baz": { + "foo": "bar", + "bar": "baz" + } + } +}"#, + r#"bar"#, + true, + Some( + r#"{ + "repositories": { + "baz": { + "foo": "bar", + "bar": "baz" + } + } +} +"#, + ), + ), + ( + r#"{ + "repositories": { + "foo": { + "baz": "qux" + }, + "baz": { + "foo": "bar", + "bar": "baz" + } + } +}"#, + r#"baz"#, + true, + Some( + r#"{ + "repositories": { + "foo": { + "baz": "qux" + } + } +} +"#, + ), + ), + ( + r#"{ + "repositories": { + } +}"#, + r#"bar"#, + true, + None, + ), + ( + r#"{ + "repositories": {} +}"#, + r#"bar"#, + true, + None, + ), + ( + r#"{ +}"#, r#"bar"#, true, None, + ), + ( + r#"{ + "repositories": { + "foo": { + "package": { "bar": "baz" } + } + } +}"#, + r#"foo"#, + true, + Some( + r#"{ + "repositories": { + } +} +"#, + ), + ), + ( + r#"{ + "repositories": { + "foo": { + "package": { "bar": "ba{z" } + } + } +}"#, + r#"bar"#, + true, + Some( + r#"{ + "repositories": { + "foo": { + "package": { "bar": "ba{z" } + } + } +} +"#, + ), + ), + ( + r#"{ + "repositories": { + "foo": { + "package": { "bar": "ba}z" } + } + } +}"#, + r#"bar"#, + true, + Some( + r#"{ + "repositories": { + "foo": { + "package": { "bar": "ba}z" } + } + } +} +"#, + ), + ), + ( + r#"{ + "repositories": [ + { + "package": { "bar": "ba[z" } + } + ] +}"#, + r#"bar"#, + false, + None, + ), + ( + r#"{ + "repositories": [ + { + "package": { "bar": "ba]z" } + } + ] +}"#, + r#"bar"#, + false, + None, + ), + ]; + for (json, name, expected, expected_content) in cases { + let mut manipulator = JsonManipulator::new(json.to_string()).unwrap(); + assert_eq!( + expected, + manipulator.remove_sub_node("repositories", name).unwrap() + ); + if let Some(expected_content) = expected_content { + assert_eq!(expected_content, manipulator.get_contents()); + } + } +} + +#[test] +#[ignore] +fn test_add_repository() { + let cases: Vec<(&str, &str, &str, PhpMixed, bool)> = vec![ + ( + r#"{ + "repositories": [ + { + "type": "git", + "url": "example.tld" + } + ] +} +"#, + r#"{ + "repositories": [ + { + "type": "path", + "url": "foo/bar" + }, + { + "type": "git", + "url": "example.tld" + } + ] +} +"#, + r#""#, + arr(&[(r#"type"#, s(r#"path"#)), (r#"url"#, s(r#"foo/bar"#))]), + false, + ), + ( + r#"{ + "repositories": [ + { + "type": "git", + "url": "example.tld" + } + ] +} +"#, + r#"{ + "repositories": [ + { + "type": "git", + "url": "example.tld" + }, + { + "type": "path", + "url": "foo/bar" + } + ] +} +"#, + r#""#, + arr(&[(r#"type"#, s(r#"path"#)), (r#"url"#, s(r#"foo/bar"#))]), + true, + ), + ( + r#"{ + "repositories": { + "0": { + "type": "git", + "url": "example.tld" + }, + "packagist.org": false + } +} +"#, + r#"{ + "repositories": [ + { + "name": "foo", + "type": "path", + "url": "foo/bar" + }, + { + "type": "git", + "url": "example.tld" + }, + { + "packagist.org": false + } + ] +} +"#, + r#"foo"#, + arr(&[(r#"type"#, s(r#"path"#)), (r#"url"#, s(r#"foo/bar"#))]), + false, + ), + ( + r#"{ + "repositories": { + "0": { + "type": "git", + "url": "example.tld" + }, + "packagist.org": false + } +} +"#, + r#"{ + "repositories": [ + { + "type": "git", + "url": "example.tld" + }, + { + "packagist.org": false + }, + { + "name": "foo", + "type": "path", + "url": "foo/bar" + } + ] +} +"#, + r#"foo"#, + arr(&[(r#"type"#, s(r#"path"#)), (r#"url"#, s(r#"foo/bar"#))]), + true, + ), + ( + r#"{ + "repositories": [ + { + "type": "git", + "url": "example.tld" + } + ] +} +"#, + r#"{ + "repositories": [ + { + "name": "foo", + "type": "path", + "url": "foo/bar" + }, + { + "type": "git", + "url": "example.tld" + } + ] +} +"#, + r#"foo"#, + arr(&[(r#"type"#, s(r#"path"#)), (r#"url"#, s(r#"foo/bar"#))]), + false, + ), + ( + r#"{ + "repositories": [ + { + "type": "git", + "url": "example.tld" + } + ] +} +"#, + r#"{ + "repositories": [ + { + "type": "git", + "url": "example.tld" + }, + { + "name": "foo", + "type": "path", + "url": "foo/bar" + } + ] +} +"#, + r#"foo"#, + arr(&[(r#"type"#, s(r#"path"#)), (r#"url"#, s(r#"foo/bar"#))]), + true, + ), + ( + r#"{ + "repositories": { + "0": { + "type": "git", + "url": "example.tld" + }, + "packagist.org": false + } +} +"#, + r#"{ + "repositories": [ + { + "type": "path", + "url": "foo/bar" + }, + { + "type": "git", + "url": "example.tld" + }, + { + "packagist.org": false + } + ] +} +"#, + r#""#, + arr(&[(r#"type"#, s(r#"path"#)), (r#"url"#, s(r#"foo/bar"#))]), + false, + ), + ( + r#"{ + "repositories": { + "0": { + "type": "git", + "url": "example.tld" + }, + "packagist.org": false + } +} +"#, + r#"{ + "repositories": [ + { + "type": "git", + "url": "example.tld" + }, + { + "packagist.org": false + }, + { + "type": "path", + "url": "foo/bar" + } + ] +} +"#, + r#""#, + arr(&[(r#"type"#, s(r#"path"#)), (r#"url"#, s(r#"foo/bar"#))]), + true, + ), + ]; + for (from, to, name, config, append) in cases { + let mut manipulator = JsonManipulator::new(from.to_string()).unwrap(); + assert!(manipulator.add_repository(name, config, append).unwrap()); + assert_eq!(to, manipulator.get_contents()); + } +} + +#[test] +#[ignore] +fn test_set_url_in_repository() { + let cases: Vec<(&str, &str, &str, &str)> = vec![ + ( + r#"{ + "repositories": { + "first": { + "type": "package", + "url": "https://first.test" + }, + "foo": { + "type": "vcs", + "url": "https://old.example.org" + }, + "bar": { + "type": "vcs", + "url": "https://other.example.org" + } + } +} +"#, + r#"{ + "repositories": { + "first": { + "type": "package", + "url": "https://new.example.org" + }, + "foo": { + "type": "vcs", + "url": "https://old.example.org" + }, + "bar": { + "type": "vcs", + "url": "https://other.example.org" + } + } +} +"#, + r#"first"#, + r#"https://new.example.org"#, + ), + ( + r#"{ + "repositories": { + "first": { + "type": "package", + "url": "https://first.test" + }, + "foo": { + "type": "vcs", + "url": "https://old.example.org" + }, + "bar": { + "type": "vcs", + "url": "https://other.example.org" + } + } +} +"#, + r#"{ + "repositories": { + "first": { + "type": "package", + "url": "https://first.test" + }, + "foo": { + "type": "vcs", + "url": "https://new.example.org" + }, + "bar": { + "type": "vcs", + "url": "https://other.example.org" + } + } +} +"#, + r#"foo"#, + r#"https://new.example.org"#, + ), + ( + r#"{ + "repositories": { + "first": { + "type": "package", + "url": "https://first.test" + }, + "foo": { + "type": "vcs", + "url": "https://old.example.org" + }, + "bar": { + "type": "vcs", + "url": "https://other.example.org" + } + } +} +"#, + r#"{ + "repositories": { + "first": { + "type": "package", + "url": "https://first.test" + }, + "foo": { + "type": "vcs", + "url": "https://old.example.org" + }, + "bar": { + "type": "vcs", + "url": "https://new.example.org" + } + } +} +"#, + r#"bar"#, + r#"https://new.example.org"#, + ), + ( + r#"{ + "repositories": [ + { + "name": "first", + "type": "package", + "url": "https://first.test" + }, + { + "name": "foo", + "type": "vcs", + "url": "https://old.example.org" + }, + { + "name": "bar", + "type": "vcs", + "url": "https://other.example.org" + } + ] +} +"#, + r#"{ + "repositories": [ + { + "name": "first", + "type": "package", + "url": "https://new.example.org" + }, + { + "name": "foo", + "type": "vcs", + "url": "https://old.example.org" + }, + { + "name": "bar", + "type": "vcs", + "url": "https://other.example.org" + } + ] +} +"#, + r#"first"#, + r#"https://new.example.org"#, + ), + ( + r#"{ + "repositories": [ + { + "name": "first", + "type": "package", + "url": "https://first.test" + }, + { + "name": "foo", + "type": "vcs", + "url": "https://old.example.org" + }, + { + "name": "bar", + "type": "vcs", + "url": "https://other.example.org" + } + ] +} +"#, + r#"{ + "repositories": [ + { + "name": "first", + "type": "package", + "url": "https://first.test" + }, + { + "name": "foo", + "type": "vcs", + "url": "https://new.example.org" + }, + { + "name": "bar", + "type": "vcs", + "url": "https://other.example.org" + } + ] +} +"#, + r#"foo"#, + r#"https://new.example.org"#, + ), + ( + r#"{ + "repositories": [ + { + "name": "first", + "type": "package", + "url": "https://first.test" + }, + { + "name": "foo", + "type": "vcs", + "url": "https://old.example.org" + }, + { + "name": "bar", + "type": "vcs", + "url": "https://other.example.org" + } + ] +} +"#, + r#"{ + "repositories": [ + { + "name": "first", + "type": "package", + "url": "https://first.test" + }, + { + "name": "foo", + "type": "vcs", + "url": "https://old.example.org" + }, + { + "name": "bar", + "type": "vcs", + "url": "https://new.example.org" + } + ] +} +"#, + r#"bar"#, + r#"https://new.example.org"#, + ), + ]; + for (from, to, name, url) in cases { + let mut manipulator = JsonManipulator::new(from.to_string()).unwrap(); + assert!(manipulator.set_repository_url(name, url).unwrap()); + assert_eq!(to, manipulator.get_contents()); + } +} + +#[test] +#[ignore] +fn test_add_list_item() { + let cases: Vec<(&str, &str, &str, PhpMixed, bool)> = vec![ + ( + r#"{}"#, + r#"{ + "main": [1] +} +"#, + r#"main"#, + PhpMixed::Int(1), + true, + ), + ( + r#"{ + "main": [ ] +} +"#, + r#"{ + "main": [1] +} +"#, + r#"main"#, + PhpMixed::Int(1), + true, + ), + ( + r#"{ + "main": [ 1 ] +} +"#, + r#"{ + "main": [ 1, 2 ] +} +"#, + r#"main"#, + PhpMixed::Int(2), + true, + ), + ( + r#"{}"#, + r#"{ + "main": [{ + "value": 1 + }] +} +"#, + r#"main"#, + arr(&[(r#"value"#, PhpMixed::Int(1))]), + true, + ), + ( + r#"{ + "main": [ ] +} +"#, + r#"{ + "main": [{ + "value": 2 + }] +} +"#, + r#"main"#, + arr(&[(r#"value"#, PhpMixed::Int(2))]), + true, + ), + ( + r#"{ + "main": [ 1 ] +} +"#, + r#"{ + "main": [ 1, { + "value": 2 + } ] +} +"#, + r#"main"#, + arr(&[(r#"value"#, PhpMixed::Int(2))]), + true, + ), + ( + r#"{ + "main": [ + ] +} +"#, + r#"{ + "main": [ + 1 + ] +} +"#, + r#"main"#, + PhpMixed::Int(1), + true, + ), + ( + r#"{ + "main": [ + 1 + ] +} +"#, + r#"{ + "main": [ + 1, + 2 + ] +} +"#, + r#"main"#, + PhpMixed::Int(2), + true, + ), + ( + r#"{ + "main": [ + ] +} +"#, + r#"{ + "main": [ + { + "value": 1 + } + ] +} +"#, + r#"main"#, + arr(&[(r#"value"#, PhpMixed::Int(1))]), + true, + ), + ( + r#"{ + "main": [ + 1 + ] +} +"#, + r#"{ + "main": [ + 1, + { + "value": 2 + } + ] +} +"#, + r#"main"#, + arr(&[(r#"value"#, PhpMixed::Int(2))]), + true, + ), + ( + r#"{}"#, + r#"{ + "main": [1] +} +"#, + r#"main"#, + PhpMixed::Int(1), + false, + ), + ( + r#"{ + "main": [ ] +} +"#, + r#"{ + "main": [1] +} +"#, + r#"main"#, + PhpMixed::Int(1), + false, + ), + ( + r#"{ + "main": [ 1 ] +} +"#, + r#"{ + "main": [ 2, 1 ] +} +"#, + r#"main"#, + PhpMixed::Int(2), + false, + ), + ( + r#"{}"#, + r#"{ + "main": [{ + "value": 1 + }] +} +"#, + r#"main"#, + arr(&[(r#"value"#, PhpMixed::Int(1))]), + false, + ), + ( + r#"{ + "main": [ ] +} +"#, + r#"{ + "main": [{ + "value": 1 + }] +} +"#, + r#"main"#, + arr(&[(r#"value"#, PhpMixed::Int(1))]), + false, + ), + ( + r#"{ + "main": [ 1 ] +} +"#, + r#"{ + "main": [ { + "value": 2 + }, 1 ] +} +"#, + r#"main"#, + arr(&[(r#"value"#, PhpMixed::Int(2))]), + false, + ), + ( + r#"{ + "main": [ + ] +} +"#, + r#"{ + "main": [ + 1 + ] +} +"#, + r#"main"#, + PhpMixed::Int(1), + false, + ), + ( + r#"{ + "main": [ + 1 + ] +} +"#, + r#"{ + "main": [ + 2, + 1 + ] +} +"#, + r#"main"#, + PhpMixed::Int(2), + false, + ), + ( + r#"{ + "main": [ + ] +} +"#, + r#"{ + "main": [ + { + "value": 1 + } + ] +} +"#, + r#"main"#, + arr(&[(r#"value"#, PhpMixed::Int(1))]), + false, + ), + ( + r#"{ + "main": [ + 1 + ] +} +"#, + r#"{ + "main": [ + { + "value": 2 + }, + 1 + ] +} +"#, + r#"main"#, + arr(&[(r#"value"#, PhpMixed::Int(2))]), + false, + ), + ]; + for (from, to, main_node, value, append) in cases { + let mut manipulator = JsonManipulator::new(from.to_string()).unwrap(); + assert!(manipulator.add_list_item(main_node, value, append).unwrap()); + assert_eq!(to, manipulator.get_contents()); + } +} + +#[test] +#[ignore] +fn test_remove_list_item() { + let cases: Vec<(&str, &str, &str, i64)> = vec![ + ( + r#"{ + "main": [ + 1, 2, 3 + ] +}"#, + r#"{ + "main": [ + 2, 3 + ] +} +"#, + r#"main"#, + 0, + ), + ( + r#"{ + "main": [ + 1, 2, 3 + ] +}"#, + r#"{ + "main": [ + 1, 3 + ] +} +"#, + r#"main"#, + 1, + ), + ( + r#"{ + "main": [ + 1, 2, 3 + ] +}"#, + r#"{ + "main": [ + 1, 2 + ] +} +"#, + r#"main"#, + 2, + ), + ( + r#"{ + "main": [ + 1, + 2, + 3 + ] +}"#, + r#"{ + "main": [ + 2, + 3 + ] +} +"#, + r#"main"#, + 0, + ), + ( + r#"{ + "main": [ + 1, + 2, + 3 + ] +}"#, + r#"{ + "main": [ + 1, + 3 + ] +} +"#, + r#"main"#, + 1, + ), + ( + r#"{ + "main": [ + 1, + 2, + 3 + ] +}"#, + r#"{ + "main": [ + 1, + 2 + ] +} +"#, + r#"main"#, + 2, + ), + ]; + for (from, to, main_node, index_to_remove) in cases { + let mut manipulator = JsonManipulator::new(from.to_string()).unwrap(); + assert!( + manipulator + .remove_list_item(main_node, index_to_remove) + .unwrap() + ); + assert_eq!(to, manipulator.get_contents()); + } +} + +#[test] +#[ignore] +fn test_insert_list_item() { + let cases: Vec<(&str, &str, &str, PhpMixed, i64)> = vec![ + ( + r#"{ +} +"#, + r#"{ + "main": [{ + "foo": 1 + }] +} +"#, + r#"main"#, + arr(&[(r#"foo"#, PhpMixed::Int(1))]), + 0, + ), + ( + r#"{ + "main": [ + ] +} +"#, + r#"{ + "main": [ + { + "foo": 1 + } + ] +} +"#, + r#"main"#, + arr(&[(r#"foo"#, PhpMixed::Int(1))]), + 0, + ), + ( + r#"{ + "main": [ + { + "foo": 2 + }, + { + "foo": 4 + } + ] +} +"#, + r#"{ + "main": [ + { + "foo": 1 + }, + { + "foo": 2 + }, + { + "foo": 4 + } + ] +} +"#, + r#"main"#, + arr(&[(r#"foo"#, PhpMixed::Int(1))]), + 0, + ), + ( + r#"{ + "main": [ + { + "foo": 2 + }, + { + "foo": 4 + } + ] +} +"#, + r#"{ + "main": [ + { + "foo": 2 + }, + { + "foo": 3 + }, + { + "foo": 4 + } + ] +} +"#, + r#"main"#, + arr(&[(r#"foo"#, PhpMixed::Int(3))]), + 1, + ), + ( + r#"{ + "main": [ + { + "foo": 2 + }, + { + "foo": 4 + } + ] +} +"#, + r#"{ + "main": [ + { + "foo": 2 + }, + { + "foo": 4 + }, + { + "foo": 5 + } + ] +} +"#, + r#"main"#, + arr(&[(r#"foo"#, PhpMixed::Int(5))]), + 2, + ), + ]; + for (from, to, main_node, value, index_to_insert_at) in cases { + let mut manipulator = JsonManipulator::new(from.to_string()).unwrap(); + assert!( + manipulator + .insert_list_item(main_node, value, index_to_insert_at) + .unwrap() + ); + assert_eq!(to, manipulator.get_contents()); + } } #[test] -#[ignore = "test body not yet ported (todo!() stub)"] +#[ignore] fn test_remove_sub_node_from_require() { - todo!() + let mut manipulator = JsonManipulator::new( + r#"{ + "repositories": [ + { + "package": { + "require": { + "this/should-not-end-up-in-root-require": "~2.0" + }, + "require-dev": { + "this/should-not-end-up-in-root-require-dev": "~2.0" + } + } + } + ], + "require": { + "package/a": "*", + "package/b": "*", + "package/c": "*" + }, + "require-dev": { + "package/d": "*" + } +}"# + .to_string(), + ) + .unwrap(); + + assert!(manipulator.remove_sub_node("require", "package/c").unwrap()); + assert!( + manipulator + .remove_sub_node("require-dev", "package/d") + .unwrap() + ); + assert_eq!( + r#"{ + "repositories": [ + { + "package": { + "require": { + "this/should-not-end-up-in-root-require": "~2.0" + }, + "require-dev": { + "this/should-not-end-up-in-root-require-dev": "~2.0" + } + } + } + ], + "require": { + "package/a": "*", + "package/b": "*" + }, + "require-dev": { + } +} +"#, + manipulator.get_contents() + ); } #[test] -#[ignore = "test body not yet ported (todo!() stub)"] +#[ignore] fn test_remove_sub_node_preserves_object_type_when_empty() { - todo!() + let mut manipulator = JsonManipulator::new( + r#"{ + "test": {"0": "foo"} +}"# + .to_string(), + ) + .unwrap(); + + assert!(manipulator.remove_sub_node("test", "0").unwrap()); + assert_eq!( + r#"{ + "test": { + } +} +"#, + manipulator.get_contents() + ); } #[test] -#[ignore = "test body not yet ported (todo!() stub)"] +#[ignore] fn test_remove_sub_node_preserves_object_type_when_empty2() { - todo!() + let mut manipulator = JsonManipulator::new( + r#"{ + "config": { + "preferred-install": {"foo/*": "source"} + } +}"# + .to_string(), + ) + .unwrap(); + + assert!( + manipulator + .remove_config_setting("preferred-install.foo/*") + .unwrap() + ); + assert_eq!( + r#"{ + "config": { + "preferred-install": { + } + } +} +"#, + manipulator.get_contents() + ); } #[test] -#[ignore = "test body not yet ported (todo!() stub)"] +#[ignore] fn test_add_sub_node_in_require() { - todo!() + let mut manipulator = JsonManipulator::new( + r#"{ + "repositories": [ + { + "package": { + "require": { + "this/should-not-end-up-in-root-require": "~2.0" + }, + "require-dev": { + "this/should-not-end-up-in-root-require-dev": "~2.0" + } + } + } + ], + "require": { + "package/a": "*", + "package/b": "*" + }, + "require-dev": { + "package/d": "*" + } +}"# + .to_string(), + ) + .unwrap(); + + assert!( + manipulator + .add_sub_node("require", "package/c", s("*"), true) + .unwrap() + ); + assert!( + manipulator + .add_sub_node("require-dev", "package/e", s("*"), true) + .unwrap() + ); + assert_eq!( + r#"{ + "repositories": [ + { + "package": { + "require": { + "this/should-not-end-up-in-root-require": "~2.0" + }, + "require-dev": { + "this/should-not-end-up-in-root-require-dev": "~2.0" + } + } + } + ], + "require": { + "package/a": "*", + "package/b": "*", + "package/c": "*" + }, + "require-dev": { + "package/d": "*", + "package/e": "*" + } +} +"#, + manipulator.get_contents() + ); } #[test] -#[ignore = "test body not yet ported (todo!() stub)"] +#[ignore] fn test_add_extra_with_package() { - todo!() + let mut manipulator = JsonManipulator::new( + r#"{ + "repositories": [ + { + "type": "package", + "package": { + "authors": [], + "extra": { + "package-xml": "package.xml" + } + } + } + ], + "extra": { + "auto-append-gitignore": true + } +}"# + .to_string(), + ) + .unwrap(); + + assert!( + manipulator + .add_property("extra.foo-bar", PhpMixed::Bool(true)) + .unwrap() + ); + assert_eq!( + r#"{ + "repositories": [ + { + "type": "package", + "package": { + "authors": [], + "extra": { + "package-xml": "package.xml" + } + } + } + ], + "extra": { + "auto-append-gitignore": true, + "foo-bar": true + } +} +"#, + manipulator.get_contents() + ); } #[test] -#[ignore = "test body not yet ported (todo!() stub)"] +#[ignore] fn test_add_config_with_package() { - todo!() + let mut manipulator = JsonManipulator::new( + r#"{ + "repositories": [ + { + "type": "package", + "package": { + "authors": [], + "extra": { + "package-xml": "package.xml" + } + } + } + ], + "config": { + "platform": { + "php": "5.3.9" + } + } +}"# + .to_string(), + ) + .unwrap(); + + assert!( + manipulator + .add_config_setting( + "preferred-install.my-organization/stable-package", + s("dist") + ) + .unwrap() + ); + assert_eq!( + r#"{ + "repositories": [ + { + "type": "package", + "package": { + "authors": [], + "extra": { + "package-xml": "package.xml" + } + } + } + ], + "config": { + "platform": { + "php": "5.3.9" + }, + "preferred-install": { + "my-organization/stable-package": "dist" + } + } +} +"#, + manipulator.get_contents() + ); } #[test] -#[ignore = "test body not yet ported (todo!() stub)"] +#[ignore] fn test_add_suggest_with_package() { - todo!() + let mut manipulator = JsonManipulator::new( + r#"{ + "repositories": [ + { + "type": "package", + "package": { + "authors": [], + "extra": { + "package-xml": "package.xml" + } + } + } + ], + "suggest": { + "package": "Description" + } +}"# + .to_string(), + ) + .unwrap(); + + assert!( + manipulator + .add_property("suggest.new-package", s("new-description")) + .unwrap() + ); + assert_eq!( + r#"{ + "repositories": [ + { + "type": "package", + "package": { + "authors": [], + "extra": { + "package-xml": "package.xml" + } + } + } + ], + "suggest": { + "package": "Description", + "new-package": "new-description" + } +} +"#, + manipulator.get_contents() + ); } #[test] -#[ignore = "test body not yet ported (todo!() stub)"] +#[ignore] fn test_add_repository_can_initialize_empty_repositories() { - todo!() + let mut manipulator = JsonManipulator::new( + r#"{ + "repositories": { + } +}"# + .to_string(), + ) + .unwrap(); + + assert!( + manipulator + .add_repository("bar", arr(&[("type", s("composer"))]), false) + .unwrap() + ); + assert_eq!( + r#"{ + "repositories": [ + { + "name": "bar", + "type": "composer" + } + ] +} +"#, + manipulator.get_contents() + ); } #[test] -#[ignore = "test body not yet ported (todo!() stub)"] +#[ignore] fn test_add_repository_can_initialize_from_scratch() { - todo!() + let mut manipulator = JsonManipulator::new("{\n\t\"a\": \"b\"\n}".to_string()).unwrap(); + + assert!( + manipulator + .add_repository("bar2", arr(&[("type", s("composer"))]), false) + .unwrap() + ); + assert_eq!( + "{\n\t\"a\": \"b\",\n\t\"repositories\": [{\n\t\t\"name\": \"bar2\",\n\t\t\"type\": \"composer\"\n\t}]\n}\n", + manipulator.get_contents() + ); } #[test] -#[ignore = "test body not yet ported (todo!() stub)"] +#[ignore] fn test_add_repository_can_append() { - todo!() + let mut manipulator = JsonManipulator::new( + r#"{ + "repositories": [ + { + "name": "foo", + "type": "vcs", + "url": "lala" + } + ] +}"# + .to_string(), + ) + .unwrap(); + + assert!( + manipulator + .add_repository("bar", arr(&[("type", s("composer"))]), true) + .unwrap() + ); + assert_eq!( + r#"{ + "repositories": [ + { + "name": "foo", + "type": "vcs", + "url": "lala" + }, + { + "name": "bar", + "type": "composer" + } + ] +} +"#, + manipulator.get_contents() + ); } #[test] -#[ignore = "test body not yet ported (todo!() stub)"] +#[ignore] fn test_add_repository_can_prepend() { - todo!() -} + let mut manipulator = JsonManipulator::new( + r#"{ + "repositories": [ + { + "name": "foo", + "type": "vcs", + "url": "lala" + } + ] +}"# + .to_string(), + ) + .unwrap(); -#[test] -#[ignore = "test body not yet ported (todo!() stub)"] -fn test_add_repository() { - todo!() + assert!( + manipulator + .add_repository("bar", arr(&[("type", s("composer"))]), false) + .unwrap() + ); + assert_eq!( + r#"{ + "repositories": [ + { + "name": "bar", + "type": "composer" + }, + { + "name": "foo", + "type": "vcs", + "url": "lala" + } + ] +} +"#, + manipulator.get_contents() + ); } #[test] -#[ignore = "test body not yet ported (todo!() stub)"] +#[ignore] fn test_add_repository_can_override_deep_repos() { - todo!() -} + let mut manipulator = JsonManipulator::new( + r#"{ + "repositories": { + "baz": { + "type": "package", + "package": {} + } + } +}"# + .to_string(), + ) + .unwrap(); -#[test] -#[ignore = "test body not yet ported (todo!() stub)"] -fn test_set_url_in_repository() { - todo!() + assert!( + manipulator + .add_repository("baz", arr(&[("type", s("composer"))]), false) + .unwrap() + ); + assert_eq!( + r#"{ + "repositories": [ + { + "name": "baz", + "type": "composer" + } + ] +} +"#, + manipulator.get_contents() + ); } #[test] -#[ignore = "test body not yet ported (todo!() stub)"] +#[ignore] fn test_insert_repository_before_and_after_by_name() { - todo!() + let mut manipulator = JsonManipulator::new( + r#"{ + "repositories": { + "alpha": { + "type": "vcs", + "url": "https://example.org/a" + }, + "omega": { + "type": "vcs", + "url": "https://example.org/o" + }, + "packagist.org": false + } +}"# + .to_string(), + ) + .unwrap(); + assert!( + manipulator + .insert_repository( + "beta", + arr(&[("type", s("vcs")), ("url", s("https://example.org/b"))]), + "omega", + 0 + ) + .unwrap() + ); + assert!( + manipulator + .insert_repository( + "gamma", + arr(&[("type", s("vcs")), ("url", s("https://example.org/g"))]), + "alpha", + 1 + ) + .unwrap() + ); + assert!( + manipulator + .insert_repository( + "alpha", + arr(&[("type", s("vcs")), ("url", s("https://example.org/alpha"))]), + "gamma", + 0 + ) + .unwrap() + ); + assert_eq!( + r#"{ + "repositories": [ + { + "name": "alpha", + "type": "vcs", + "url": "https://example.org/alpha" + }, + { + "name": "gamma", + "type": "vcs", + "url": "https://example.org/g" + }, + { + "name": "beta", + "type": "vcs", + "url": "https://example.org/b" + }, + { + "name": "omega", + "type": "vcs", + "url": "https://example.org/o" + }, + { + "packagist.org": false + } + ] +} +"#, + manipulator.get_contents() + ); } #[test] -#[ignore = "test body not yet ported (todo!() stub)"] +#[ignore] fn test_remove_repository_removes_from_assoc_but_does_not_converts_from_assoc_to_list() { - todo!() + let mut manipulator = JsonManipulator::new( + r#"{ + "repositories": { + "baz": { + "type": "package", + "package": {} + }, + "packagist.org": false + } +}"# + .to_string(), + ) + .unwrap(); + + assert!(manipulator.remove_repository("baz").unwrap()); + assert_eq!( + r#"{ + "repositories": { + "packagist.org": false + } +} +"#, + manipulator.get_contents() + ); } #[test] -#[ignore = "test body not yet ported (todo!() stub)"] +#[ignore] fn test_remove_repository_removes_from_list() { - todo!() + let mut manipulator = JsonManipulator::new( + r#"{ + "repositories": [ + { + "name": "baz", + "type": "package", + "package": { + } + }, + { + "packagist.org": false + } + ] +}"# + .to_string(), + ) + .unwrap(); + + assert!(manipulator.remove_repository("baz").unwrap()); + assert_eq!( + r#"{ + "repositories": [ + { + "packagist.org": false + } + ] +} +"#, + manipulator.get_contents() + ); } #[test] -#[ignore = "test body not yet ported (todo!() stub)"] +#[ignore] fn test_add_repository_converts_from_assoc_to_list() { - todo!() + let mut manipulator = JsonManipulator::new( + r#"{ + "repositories": { + "baz": { + "type": "package", + "package": {} + }, + "packagist.org": false + } +}"# + .to_string(), + ) + .unwrap(); + + assert!( + manipulator + .add_repository("foo", arr(&[("type", s("composer"))]), false) + .unwrap() + ); + assert_eq!( + r#"{ + "repositories": [ + { + "name": "baz", + "type": "package", + "package": { + } + }, + { + "packagist.org": false + }, + { + "name": "foo", + "type": "composer" + } + ] +} +"#, + manipulator.get_contents() + ); } #[test] -#[ignore = "test body not yet ported (todo!() stub)"] +#[ignore] fn test_add_config_setting_escapes() { - todo!() + let mut manipulator = JsonManipulator::new( + r#"{ + "config": { + } +}"# + .to_string(), + ) + .unwrap(); + + assert!(manipulator.add_config_setting("test", s(r#"a\b"#)).unwrap()); + assert!( + manipulator + .add_config_setting("test2", s("a\nb\u{0C}a")) + .unwrap() + ); + assert_eq!( + r#"{ + "config": { + "test": "a\\b", + "test2": "a\nb\fa" + } +} +"#, + manipulator.get_contents() + ); } #[test] -#[ignore = "test body not yet ported (todo!() stub)"] +#[ignore] fn test_add_config_setting_works_from_scratch() { - todo!() + let mut manipulator = JsonManipulator::new( + r#"{ +}"# + .to_string(), + ) + .unwrap(); + + assert!(manipulator.add_config_setting("foo.bar", s("baz")).unwrap()); + assert_eq!( + r#"{ + "config": { + "foo": { + "bar": "baz" + } + } +} +"#, + manipulator.get_contents() + ); } #[test] -#[ignore = "test body not yet ported (todo!() stub)"] +#[ignore] fn test_add_config_setting_can_add() { - todo!() + let mut manipulator = JsonManipulator::new( + r#"{ + "config": { + "foo": "bar" + } +}"# + .to_string(), + ) + .unwrap(); + + assert!(manipulator.add_config_setting("bar", s("baz")).unwrap()); + assert_eq!( + r#"{ + "config": { + "foo": "bar", + "bar": "baz" + } +} +"#, + manipulator.get_contents() + ); } #[test] -#[ignore = "test body not yet ported (todo!() stub)"] +#[ignore] fn test_add_config_setting_can_overwrite() { - todo!() + let mut manipulator = JsonManipulator::new( + r#"{ + "config": { + "foo": "bar", + "bar": "baz" + } +}"# + .to_string(), + ) + .unwrap(); + + assert!(manipulator.add_config_setting("foo", s("zomg")).unwrap()); + assert_eq!( + r#"{ + "config": { + "foo": "zomg", + "bar": "baz" + } +} +"#, + manipulator.get_contents() + ); } #[test] -#[ignore = "test body not yet ported (todo!() stub)"] +#[ignore] fn test_add_config_setting_can_overwrite_numbers() { - todo!() + let mut manipulator = JsonManipulator::new( + r#"{ + "config": { + "foo": 500 + } +}"# + .to_string(), + ) + .unwrap(); + + assert!( + manipulator + .add_config_setting("foo", PhpMixed::Int(50)) + .unwrap() + ); + assert_eq!( + r#"{ + "config": { + "foo": 50 + } +} +"#, + manipulator.get_contents() + ); } #[test] -#[ignore = "test body not yet ported (todo!() stub)"] +#[ignore] fn test_add_config_setting_can_overwrite_arrays() { - todo!() + let mut manipulator = JsonManipulator::new( + r#"{ + "config": { + "github-oauth": { + "github.com": "foo" + }, + "github-protocols": ["https"] + } +}"# + .to_string(), + ) + .unwrap(); + + assert!( + manipulator + .add_config_setting( + "github-protocols", + PhpMixed::List(vec![s("https"), s("http")]) + ) + .unwrap() + ); + assert_eq!( + r#"{ + "config": { + "github-oauth": { + "github.com": "foo" + }, + "github-protocols": ["https", "http"] + } +} +"#, + manipulator.get_contents() + ); + + assert!( + manipulator + .add_config_setting( + "github-oauth", + arr(&[("github.com", s("bar")), ("alt.example.org", s("baz"))]) + ) + .unwrap() + ); + assert_eq!( + r#"{ + "config": { + "github-oauth": { + "github.com": "bar", + "alt.example.org": "baz" + }, + "github-protocols": ["https", "http"] + } +} +"#, + manipulator.get_contents() + ); } #[test] -#[ignore = "test body not yet ported (todo!() stub)"] +#[ignore] fn test_add_config_setting_can_add_sub_key_in_empty_config() { - todo!() + let mut manipulator = JsonManipulator::new( + r#"{ + "config": { + } +}"# + .to_string(), + ) + .unwrap(); + + assert!( + manipulator + .add_config_setting("github-oauth.bar", s("baz")) + .unwrap() + ); + assert_eq!( + r#"{ + "config": { + "github-oauth": { + "bar": "baz" + } + } +} +"#, + manipulator.get_contents() + ); } #[test] -#[ignore = "test body not yet ported (todo!() stub)"] +#[ignore] fn test_add_config_setting_can_add_sub_key_in_empty_val() { - todo!() + let mut manipulator = JsonManipulator::new( + r#"{ + "config": { + "github-oauth": {}, + "github-oauth2": { + } + } +}"# + .to_string(), + ) + .unwrap(); + + assert!( + manipulator + .add_config_setting("github-oauth.bar", s("baz")) + .unwrap() + ); + assert!( + manipulator + .add_config_setting("github-oauth2.a.bar", s("baz2")) + .unwrap() + ); + assert!( + manipulator + .add_config_setting("github-oauth3.b", s("c")) + .unwrap() + ); + assert_eq!( + r#"{ + "config": { + "github-oauth": { + "bar": "baz" + }, + "github-oauth2": { + "a.bar": "baz2" + }, + "github-oauth3": { + "b": "c" + } + } +} +"#, + manipulator.get_contents() + ); } #[test] -#[ignore = "test body not yet ported (todo!() stub)"] +#[ignore] fn test_add_config_setting_can_add_sub_key_in_hash() { - todo!() + let mut manipulator = JsonManipulator::new( + r#"{ + "config": { + "github-oauth": { + "github.com": "foo" + } + } +}"# + .to_string(), + ) + .unwrap(); + + assert!( + manipulator + .add_config_setting("github-oauth.bar", s("baz")) + .unwrap() + ); + assert_eq!( + r#"{ + "config": { + "github-oauth": { + "github.com": "foo", + "bar": "baz" + } + } +} +"#, + manipulator.get_contents() + ); } #[test] -#[ignore = "test body not yet ported (todo!() stub)"] +#[ignore] fn test_add_root_setting_does_not_break_dots() { - todo!() + let mut manipulator = JsonManipulator::new( + r#"{ + "github-oauth": { + "github.com": "foo" + } +}"# + .to_string(), + ) + .unwrap(); + + assert!( + manipulator + .add_sub_node("github-oauth", "bar", s("baz"), true) + .unwrap() + ); + assert_eq!( + r#"{ + "github-oauth": { + "github.com": "foo", + "bar": "baz" + } +} +"#, + manipulator.get_contents() + ); } #[test] -#[ignore = "test body not yet ported (todo!() stub)"] +#[ignore] fn test_remove_config_setting_can_remove_sub_key_in_hash() { - todo!() + let mut manipulator = JsonManipulator::new( + r#"{ + "config": { + "github-oauth": { + "github.com": "foo", + "bar": "baz" + } + } +}"# + .to_string(), + ) + .unwrap(); + + assert!( + manipulator + .remove_config_setting("github-oauth.bar") + .unwrap() + ); + assert_eq!( + r#"{ + "config": { + "github-oauth": { + "github.com": "foo" + } + } +} +"#, + manipulator.get_contents() + ); } #[test] -#[ignore = "test body not yet ported (todo!() stub)"] +#[ignore] fn test_remove_config_setting_can_remove_sub_key_in_hash_with_siblings() { - todo!() + let mut manipulator = JsonManipulator::new( + r#"{ + "config": { + "foo": "bar", + "github-oauth": { + "github.com": "foo", + "bar": "baz" + } + } +}"# + .to_string(), + ) + .unwrap(); + + assert!( + manipulator + .remove_config_setting("github-oauth.bar") + .unwrap() + ); + assert_eq!( + r#"{ + "config": { + "foo": "bar", + "github-oauth": { + "github.com": "foo" + } + } +} +"#, + manipulator.get_contents() + ); } #[test] -#[ignore = "test body not yet ported (todo!() stub)"] +#[ignore] fn test_add_main_key() { - todo!() + let mut manipulator = JsonManipulator::new( + r#"{ + "foo": "bar" +}"# + .to_string(), + ) + .unwrap(); + + assert!(manipulator.add_main_key("bar", s("baz")).unwrap()); + assert_eq!( + r#"{ + "foo": "bar", + "bar": "baz" +} +"#, + manipulator.get_contents() + ); } #[test] -#[ignore = "test body not yet ported (todo!() stub)"] +#[ignore] fn test_add_main_key_with_content_having_dollar_sign_followed_by_digit() { - todo!() + let mut manipulator = JsonManipulator::new( + r#"{ + "foo": "bar" +}"# + .to_string(), + ) + .unwrap(); + + assert!(manipulator.add_main_key("bar", s("$1baz")).unwrap()); + assert_eq!( + r#"{ + "foo": "bar", + "bar": "$1baz" +} +"#, + manipulator.get_contents() + ); } #[test] -#[ignore = "test body not yet ported (todo!() stub)"] +#[ignore] fn test_add_main_key_with_content_having_dollar_sign_followed_by_digit2() { - todo!() + let mut manipulator = JsonManipulator::new("{}".to_string()).unwrap(); + + assert!(manipulator.add_main_key("foo", s("$1bar")).unwrap()); + assert_eq!( + r#"{ + "foo": "$1bar" +} +"#, + manipulator.get_contents() + ); } #[test] -#[ignore = "test body not yet ported (todo!() stub)"] +#[ignore] fn test_update_main_key() { - todo!() + let mut manipulator = JsonManipulator::new( + r#"{ + "foo": "bar" +}"# + .to_string(), + ) + .unwrap(); + + assert!(manipulator.add_main_key("foo", s("baz")).unwrap()); + assert_eq!( + r#"{ + "foo": "baz" +} +"#, + manipulator.get_contents() + ); } #[test] -#[ignore = "test body not yet ported (todo!() stub)"] +#[ignore] fn test_update_main_key2() { - todo!() + let mut manipulator = JsonManipulator::new( + r#"{ + "a": { + "foo": "bar", + "baz": "qux" + }, + "foo": "bar", + "baz": "bar" +}"# + .to_string(), + ) + .unwrap(); + + assert!(manipulator.add_main_key("foo", s("baz")).unwrap()); + assert!(manipulator.add_main_key("baz", s("quux")).unwrap()); + assert_eq!( + r#"{ + "a": { + "foo": "bar", + "baz": "qux" + }, + "foo": "baz", + "baz": "quux" +} +"#, + manipulator.get_contents() + ); } #[test] -#[ignore = "test body not yet ported (todo!() stub)"] +#[ignore] fn test_update_main_key3() { - todo!() + let mut manipulator = JsonManipulator::new( + r#"{ + "require": { + "php": "5.*" + }, + "require-dev": { + "foo": "bar" + } +}"# + .to_string(), + ) + .unwrap(); + + assert!( + manipulator + .add_main_key("require-dev", arr(&[("foo", s("qux"))])) + .unwrap() + ); + assert_eq!( + r#"{ + "require": { + "php": "5.*" + }, + "require-dev": { + "foo": "qux" + } +} +"#, + manipulator.get_contents() + ); } #[test] -#[ignore = "test body not yet ported (todo!() stub)"] +#[ignore] fn test_update_main_key_with_content_having_dollar_sign_followed_by_digit() { - todo!() + let mut manipulator = JsonManipulator::new( + r#"{ + "foo": "bar" +}"# + .to_string(), + ) + .unwrap(); + + assert!(manipulator.add_main_key("foo", s("$1bar")).unwrap()); + assert_eq!( + r#"{ + "foo": "$1bar" +} +"#, + manipulator.get_contents() + ); } #[test] -#[ignore = "test body not yet ported (todo!() stub)"] +#[ignore] fn test_remove_main_key() { - todo!() + let mut manipulator = JsonManipulator::new( + r#"{ + "repositories": [ + { + "package": { + "require": { + "this/should-not-end-up-in-root-require": "~2.0" + }, + "require-dev": { + "this/should-not-end-up-in-root-require-dev": "~2.0" + } + } + } + ], + "require": { + "package/a": "*", + "package/b": "*", + "package/c": "*" + }, + "foo": "bar", + "require-dev": { + "package/d": "*" + } +}"# + .to_string(), + ) + .unwrap(); + + assert!(manipulator.remove_main_key("repositories").unwrap()); + assert_eq!( + r#"{ + "require": { + "package/a": "*", + "package/b": "*", + "package/c": "*" + }, + "foo": "bar", + "require-dev": { + "package/d": "*" + } +} +"#, + manipulator.get_contents() + ); + + assert!(manipulator.remove_main_key("foo").unwrap()); + assert_eq!( + r#"{ + "require": { + "package/a": "*", + "package/b": "*", + "package/c": "*" + }, + "require-dev": { + "package/d": "*" + } +} +"#, + manipulator.get_contents() + ); + + assert!(manipulator.remove_main_key("require").unwrap()); + assert!(manipulator.remove_main_key("require-dev").unwrap()); + assert_eq!( + r#"{ +} +"#, + manipulator.get_contents() + ); } #[test] -#[ignore = "test body not yet ported (todo!() stub)"] +#[ignore] fn test_remove_main_key_if_empty() { - todo!() + let mut manipulator = JsonManipulator::new( + r#"{ + "repositories": [ + ], + "require": { + "package/a": "*", + "package/b": "*", + "package/c": "*" + }, + "foo": "bar", + "require-dev": { + } +}"# + .to_string(), + ) + .unwrap(); + + assert!( + manipulator + .remove_main_key_if_empty("repositories") + .unwrap() + ); + assert_eq!( + r#"{ + "require": { + "package/a": "*", + "package/b": "*", + "package/c": "*" + }, + "foo": "bar", + "require-dev": { + } +} +"#, + manipulator.get_contents() + ); + + assert!(manipulator.remove_main_key_if_empty("foo").unwrap()); + assert!(manipulator.remove_main_key_if_empty("require").unwrap()); + assert!(manipulator.remove_main_key_if_empty("require-dev").unwrap()); + assert_eq!( + r#"{ + "require": { + "package/a": "*", + "package/b": "*", + "package/c": "*" + }, + "foo": "bar" +} +"#, + manipulator.get_contents() + ); } #[test] -#[ignore = "test body not yet ported (todo!() stub)"] +#[ignore] fn test_remove_main_key_removes_key_where_value_is_null() { - todo!() + let mut manipulator = JsonManipulator::new(r#"{"foo":9000,"bar":null}"#.to_string()).unwrap(); + + manipulator.remove_main_key("bar").unwrap(); + + let expected = JsonFile::encode(&arr(&[("foo", PhpMixed::Int(9000))])); + + assert_eq!( + JsonFile::parse_json(Some(&expected), None).unwrap(), + JsonFile::parse_json(Some(&manipulator.get_contents()), None).unwrap() + ); } #[test] -#[ignore = "test body not yet ported (todo!() stub)"] +#[ignore] fn test_indent_detection() { - todo!() -} + let mut manipulator = + JsonManipulator::new("{\n\n \"require\": {\n \"php\": \"5.*\"\n }\n}".to_string()) + .unwrap(); -#[test] -#[ignore = "test body not yet ported (todo!() stub)"] -fn test_remove_main_key_at_end_of_file() { - todo!() + assert!( + manipulator + .add_main_key("require-dev", arr(&[("foo", s("qux"))])) + .unwrap() + ); + assert_eq!( + "{\n\n \"require\": {\n \"php\": \"5.*\"\n },\n \"require-dev\": {\n \"foo\": \"qux\"\n }\n}\n", + manipulator.get_contents() + ); } #[test] -#[ignore = "test body not yet ported (todo!() stub)"] -fn test_add_list_item() { - todo!() +#[ignore] +fn test_remove_main_key_at_end_of_file() { + let mut manipulator = JsonManipulator::new( + "{\n \"require\": {\n \"package/a\": \"*\"\n }\n}\n".to_string(), + ) + .unwrap(); + assert!(manipulator.add_main_key("homepage", s("http...")).unwrap()); + assert!(manipulator.add_main_key("license", s("mit")).unwrap()); + assert_eq!( + r#"{ + "require": { + "package/a": "*" + }, + "homepage": "http...", + "license": "mit" } +"#, + manipulator.get_contents() + ); -#[test] -#[ignore = "test body not yet ported (todo!() stub)"] -fn test_remove_list_item() { - todo!() + assert!(manipulator.remove_main_key("homepage").unwrap()); + assert!(manipulator.remove_main_key("license").unwrap()); + assert_eq!( + r#"{ + "require": { + "package/a": "*" + } } - -#[test] -#[ignore = "test body not yet ported (todo!() stub)"] -fn test_insert_list_item() { - todo!() +"#, + manipulator.get_contents() + ); } #[test] -#[ignore = "test body not yet ported (todo!() stub)"] +#[ignore] fn test_escaped_unicode_does_not_cause_backtrack_limit_error_github_issue8131() { - todo!() + let mut manipulator = JsonManipulator::new( + r#"{ + "description": "Some U\u00F1icode", + "require": { + "foo/bar": "^1.0" + } +}"# + .to_string(), + ) + .unwrap(); + + assert!( + manipulator + .add_link("require", "foo/baz", "^1.0", false) + .unwrap() + ); + assert_eq!( + r#"{ + "description": "Some U\u00F1icode", + "require": { + "foo/bar": "^1.0", + "foo/baz": "^1.0" + } +} +"#, + manipulator.get_contents() + ); } #[test] -#[ignore = "test body not yet ported (todo!() stub)"] +#[ignore] fn test_large_file_does_not_cause_backtrack_limit_error_github_issue9595() { - todo!() + let mut manipulator = JsonManipulator::new( + r#"{ + "name": "leoloso/pop", + "require": { + "php": "^7.4|^8.0", + "ext-mbstring": "*", + "brain/cortex": "~1.0.0", + "composer/installers": "~1.0", + "composer/semver": "^1.5", + "erusev/parsedown": "^1.7", + "guzzlehttp/guzzle": "~6.3", + "jrfnl/php-cast-to-type": "^2.0", + "league/pipeline": "^1.0", + "lkwdwrd/wp-muplugin-loader": "dev-feature-composer-v2", + "obsidian/polyfill-hrtime": "^0.1", + "psr/cache": "^1.0", + "symfony/cache": "^5.1", + "symfony/config": "^5.1", + "symfony/dependency-injection": "^5.1", + "symfony/dotenv": "^5.1", + "symfony/expression-language": "^5.1", + "symfony/polyfill-php72": "^1.18", + "symfony/polyfill-php73": "^1.18", + "symfony/polyfill-php74": "^1.18", + "symfony/polyfill-php80": "^1.18", + "symfony/property-access": "^5.1", + "symfony/yaml": "^5.1" + }, + "require-dev": { + "johnpbloch/wordpress": ">=5.5", + "phpstan/phpstan": "^0.12", + "phpunit/phpunit": ">=9.3", + "rector/rector": "^0.9", + "squizlabs/php_codesniffer": "^3.0", + "symfony/var-dumper": "^5.1", + "symplify/monorepo-builder": "^9.0", + "szepeviktor/phpstan-wordpress": "^0.6.2" + }, + "autoload": { + "psr-4": { + "GraphQLAPI\\ConvertCaseDirectives\\": "layers/GraphQLAPIForWP/plugins/convert-case-directives/src", + "GraphQLAPI\\GraphQLAPI\\": "layers/GraphQLAPIForWP/plugins/graphql-api-for-wp/src", + "GraphQLAPI\\SchemaFeedback\\": "layers/GraphQLAPIForWP/plugins/schema-feedback/src", + "GraphQLByPoP\\GraphQLClientsForWP\\": "layers/GraphQLByPoP/packages/graphql-clients-for-wp/src", + "GraphQLByPoP\\GraphQLEndpointForWP\\": "layers/GraphQLByPoP/packages/graphql-endpoint-for-wp/src", + "GraphQLByPoP\\GraphQLParser\\": "layers/GraphQLByPoP/packages/graphql-parser/src", + "GraphQLByPoP\\GraphQLQuery\\": "layers/GraphQLByPoP/packages/graphql-query/src", + "GraphQLByPoP\\GraphQLRequest\\": "layers/GraphQLByPoP/packages/graphql-request/src", + "GraphQLByPoP\\GraphQLServer\\": "layers/GraphQLByPoP/packages/graphql-server/src", + "Leoloso\\ExamplesForPoP\\": "layers/Misc/packages/examples-for-pop/src", + "PoPSchema\\BasicDirectives\\": "layers/Schema/packages/basic-directives/src", + "PoPSchema\\BlockMetadataWP\\": "layers/Schema/packages/block-metadata-for-wp/src", + "PoPSchema\\CDNDirective\\": "layers/Schema/packages/cdn-directive/src", + "PoPSchema\\CategoriesWP\\": "layers/Schema/packages/categories-wp/src", + "PoPSchema\\Categories\\": "layers/Schema/packages/categories/src", + "PoPSchema\\CommentMetaWP\\": "layers/Schema/packages/commentmeta-wp/src", + "PoPSchema\\CommentMeta\\": "layers/Schema/packages/commentmeta/src", + "PoPSchema\\CommentMutationsWP\\": "layers/Schema/packages/comment-mutations-wp/src", + "PoPSchema\\CommentMutations\\": "layers/Schema/packages/comment-mutations/src", + "PoPSchema\\CommentsWP\\": "layers/Schema/packages/comments-wp/src", + "PoPSchema\\Comments\\": "layers/Schema/packages/comments/src", + "PoPSchema\\ConvertCaseDirectives\\": "layers/Schema/packages/convert-case-directives/src", + "PoPSchema\\CustomPostMediaMutationsWP\\": "layers/Schema/packages/custompostmedia-mutations-wp/src", + "PoPSchema\\CustomPostMediaMutations\\": "layers/Schema/packages/custompostmedia-mutations/src", + "PoPSchema\\CustomPostMediaWP\\": "layers/Schema/packages/custompostmedia-wp/src", + "PoPSchema\\CustomPostMedia\\": "layers/Schema/packages/custompostmedia/src", + "PoPSchema\\CustomPostMetaWP\\": "layers/Schema/packages/custompostmeta-wp/src", + "PoPSchema\\CustomPostMeta\\": "layers/Schema/packages/custompostmeta/src", + "PoPSchema\\CustomPostMutationsWP\\": "layers/Schema/packages/custompost-mutations-wp/src", + "PoPSchema\\CustomPostMutations\\": "layers/Schema/packages/custompost-mutations/src", + "PoPSchema\\CustomPostsWP\\": "layers/Schema/packages/customposts-wp/src", + "PoPSchema\\CustomPosts\\": "layers/Schema/packages/customposts/src", + "PoPSchema\\EventMutationsWPEM\\": "layers/Schema/packages/event-mutations-wp-em/src", + "PoPSchema\\EventMutations\\": "layers/Schema/packages/event-mutations/src", + "PoPSchema\\EventsWPEM\\": "layers/Schema/packages/events-wp-em/src", + "PoPSchema\\Events\\": "layers/Schema/packages/events/src", + "PoPSchema\\EverythingElseWP\\": "layers/Schema/packages/everythingelse-wp/src", + "PoPSchema\\EverythingElse\\": "layers/Schema/packages/everythingelse/src", + "PoPSchema\\GenericCustomPosts\\": "layers/Schema/packages/generic-customposts/src", + "PoPSchema\\GoogleTranslateDirectiveForCustomPosts\\": "layers/Schema/packages/google-translate-directive-for-customposts/src", + "PoPSchema\\GoogleTranslateDirective\\": "layers/Schema/packages/google-translate-directive/src", + "PoPSchema\\HighlightsWP\\": "layers/Schema/packages/highlights-wp/src", + "PoPSchema\\Highlights\\": "layers/Schema/packages/highlights/src", + "PoPSchema\\LocationPostsWP\\": "layers/Schema/packages/locationposts-wp/src", + "PoPSchema\\LocationPosts\\": "layers/Schema/packages/locationposts/src", + "PoPSchema\\LocationsWPEM\\": "layers/Schema/packages/locations-wp-em/src", + "PoPSchema\\Locations\\": "layers/Schema/packages/locations/src", + "PoPSchema\\MediaWP\\": "layers/Schema/packages/media-wp/src", + "PoPSchema\\Media\\": "layers/Schema/packages/media/src", + "PoPSchema\\MenusWP\\": "layers/Schema/packages/menus-wp/src", + "PoPSchema\\Menus\\": "layers/Schema/packages/menus/src", + "PoPSchema\\MetaQueryWP\\": "layers/Schema/packages/metaquery-wp/src", + "PoPSchema\\MetaQuery\\": "layers/Schema/packages/metaquery/src", + "PoPSchema\\Meta\\": "layers/Schema/packages/meta/src", + "PoPSchema\\NotificationsWP\\": "layers/Schema/packages/notifications-wp/src", + "PoPSchema\\Notifications\\": "layers/Schema/packages/notifications/src", + "PoPSchema\\PagesWP\\": "layers/Schema/packages/pages-wp/src", + "PoPSchema\\Pages\\": "layers/Schema/packages/pages/src", + "PoPSchema\\PostMutations\\": "layers/Schema/packages/post-mutations/src", + "PoPSchema\\PostTagsWP\\": "layers/Schema/packages/post-tags-wp/src", + "PoPSchema\\PostTags\\": "layers/Schema/packages/post-tags/src", + "PoPSchema\\PostsWP\\": "layers/Schema/packages/posts-wp/src", + "PoPSchema\\Posts\\": "layers/Schema/packages/posts/src", + "PoPSchema\\QueriedObjectWP\\": "layers/Schema/packages/queriedobject-wp/src", + "PoPSchema\\QueriedObject\\": "layers/Schema/packages/queriedobject/src", + "PoPSchema\\SchemaCommons\\": "layers/Schema/packages/schema-commons/src", + "PoPSchema\\StancesWP\\": "layers/Schema/packages/stances-wp/src", + "PoPSchema\\Stances\\": "layers/Schema/packages/stances/src", + "PoPSchema\\TagsWP\\": "layers/Schema/packages/tags-wp/src", + "PoPSchema\\Tags\\": "layers/Schema/packages/tags/src", + "PoPSchema\\TaxonomiesWP\\": "layers/Schema/packages/taxonomies-wp/src", + "PoPSchema\\Taxonomies\\": "layers/Schema/packages/taxonomies/src", + "PoPSchema\\TaxonomyMetaWP\\": "layers/Schema/packages/taxonomymeta-wp/src", + "PoPSchema\\TaxonomyMeta\\": "layers/Schema/packages/taxonomymeta/src", + "PoPSchema\\TaxonomyQueryWP\\": "layers/Schema/packages/taxonomyquery-wp/src", + "PoPSchema\\TaxonomyQuery\\": "layers/Schema/packages/taxonomyquery/src", + "PoPSchema\\TranslateDirectiveACL\\": "layers/Schema/packages/translate-directive-acl/src", + "PoPSchema\\TranslateDirective\\": "layers/Schema/packages/translate-directive/src", + "PoPSchema\\UserMetaWP\\": "layers/Schema/packages/usermeta-wp/src", + "PoPSchema\\UserMeta\\": "layers/Schema/packages/usermeta/src", + "PoPSchema\\UserRolesACL\\": "layers/Schema/packages/user-roles-acl/src", + "PoPSchema\\UserRolesAccessControl\\": "layers/Schema/packages/user-roles-access-control/src", + "PoPSchema\\UserRolesWP\\": "layers/Schema/packages/user-roles-wp/src", + "PoPSchema\\UserRoles\\": "layers/Schema/packages/user-roles/src", + "PoPSchema\\UserStateAccessControl\\": "layers/Schema/packages/user-state-access-control/src", + "PoPSchema\\UserStateMutationsWP\\": "layers/Schema/packages/user-state-mutations-wp/src", + "PoPSchema\\UserStateMutations\\": "layers/Schema/packages/user-state-mutations/src", + "PoPSchema\\UserStateWP\\": "layers/Schema/packages/user-state-wp/src", + "PoPSchema\\UserState\\": "layers/Schema/packages/user-state/src", + "PoPSchema\\UsersWP\\": "layers/Schema/packages/users-wp/src", + "PoPSchema\\Users\\": "layers/Schema/packages/users/src", + "PoPSitesWassup\\CommentMutations\\": "layers/Wassup/packages/comment-mutations/src", + "PoPSitesWassup\\ContactUsMutations\\": "layers/Wassup/packages/contactus-mutations/src", + "PoPSitesWassup\\ContactUserMutations\\": "layers/Wassup/packages/contactuser-mutations/src", + "PoPSitesWassup\\CustomPostLinkMutations\\": "layers/Wassup/packages/custompostlink-mutations/src", + "PoPSitesWassup\\CustomPostMutations\\": "layers/Wassup/packages/custompost-mutations/src", + "PoPSitesWassup\\EventLinkMutations\\": "layers/Wassup/packages/eventlink-mutations/src", + "PoPSitesWassup\\EventMutations\\": "layers/Wassup/packages/event-mutations/src", + "PoPSitesWassup\\EverythingElseMutations\\": "layers/Wassup/packages/everythingelse-mutations/src", + "PoPSitesWassup\\FlagMutations\\": "layers/Wassup/packages/flag-mutations/src", + "PoPSitesWassup\\FormMutations\\": "layers/Wassup/packages/form-mutations/src", + "PoPSitesWassup\\GravityFormsMutations\\": "layers/Wassup/packages/gravityforms-mutations/src", + "PoPSitesWassup\\HighlightMutations\\": "layers/Wassup/packages/highlight-mutations/src", + "PoPSitesWassup\\LocationMutations\\": "layers/Wassup/packages/location-mutations/src", + "PoPSitesWassup\\LocationPostLinkMutations\\": "layers/Wassup/packages/locationpostlink-mutations/src", + "PoPSitesWassup\\LocationPostMutations\\": "layers/Wassup/packages/locationpost-mutations/src", + "PoPSitesWassup\\NewsletterMutations\\": "layers/Wassup/packages/newsletter-mutations/src", + "PoPSitesWassup\\NotificationMutations\\": "layers/Wassup/packages/notification-mutations/src", + "PoPSitesWassup\\PostLinkMutations\\": "layers/Wassup/packages/postlink-mutations/src", + "PoPSitesWassup\\PostMutations\\": "layers/Wassup/packages/post-mutations/src", + "PoPSitesWassup\\ShareMutations\\": "layers/Wassup/packages/share-mutations/src", + "PoPSitesWassup\\SocialNetworkMutations\\": "layers/Wassup/packages/socialnetwork-mutations/src", + "PoPSitesWassup\\StanceMutations\\": "layers/Wassup/packages/stance-mutations/src", + "PoPSitesWassup\\SystemMutations\\": "layers/Wassup/packages/system-mutations/src", + "PoPSitesWassup\\UserStateMutations\\": "layers/Wassup/packages/user-state-mutations/src", + "PoPSitesWassup\\VolunteerMutations\\": "layers/Wassup/packages/volunteer-mutations/src", + "PoPSitesWassup\\Wassup\\": "layers/Wassup/packages/wassup/src", + "PoP\\APIClients\\": "layers/API/packages/api-clients/src", + "PoP\\APIEndpointsForWP\\": "layers/API/packages/api-endpoints-for-wp/src", + "PoP\\APIEndpoints\\": "layers/API/packages/api-endpoints/src", + "PoP\\APIMirrorQuery\\": "layers/API/packages/api-mirrorquery/src", + "PoP\\API\\": "layers/API/packages/api/src", + "PoP\\AccessControl\\": "layers/Engine/packages/access-control/src", + "PoP\\ApplicationWP\\": "layers/SiteBuilder/packages/application-wp/src", + "PoP\\Application\\": "layers/SiteBuilder/packages/application/src", + "PoP\\Base36Definitions\\": "layers/SiteBuilder/packages/definitions-base36/src", + "PoP\\CacheControl\\": "layers/Engine/packages/cache-control/src", + "PoP\\ComponentModel\\": "layers/Engine/packages/component-model/src", + "PoP\\ConfigurableSchemaFeedback\\": "layers/Engine/packages/configurable-schema-feedback/src", + "PoP\\ConfigurationComponentModel\\": "layers/SiteBuilder/packages/component-model-configuration/src", + "PoP\\DefinitionPersistence\\": "layers/SiteBuilder/packages/definitionpersistence/src", + "PoP\\Definitions\\": "layers/Engine/packages/definitions/src", + "PoP\\EmojiDefinitions\\": "layers/SiteBuilder/packages/definitions-emoji/src", + "PoP\\EngineWP\\": "layers/Engine/packages/engine-wp/src", + "PoP\\Engine\\": "layers/Engine/packages/engine/src", + "PoP\\FieldQuery\\": "layers/Engine/packages/field-query/src", + "PoP\\FileStore\\": "layers/Engine/packages/filestore/src", + "PoP\\FunctionFields\\": "layers/Engine/packages/function-fields/src", + "PoP\\GraphQLAPI\\": "layers/API/packages/api-graphql/src", + "PoP\\GuzzleHelpers\\": "layers/Engine/packages/guzzle-helpers/src", + "PoP\\HooksWP\\": "layers/Engine/packages/hooks-wp/src", + "PoP\\Hooks\\": "layers/Engine/packages/hooks/src", + "PoP\\LooseContracts\\": "layers/Engine/packages/loosecontracts/src", + "PoP\\MandatoryDirectivesByConfiguration\\": "layers/Engine/packages/mandatory-directives-by-configuration/src", + "PoP\\ModuleRouting\\": "layers/Engine/packages/modulerouting/src", + "PoP\\Multisite\\": "layers/SiteBuilder/packages/multisite/src", + "PoP\\PoP\\": "src", + "PoP\\QueryParsing\\": "layers/Engine/packages/query-parsing/src", + "PoP\\RESTAPI\\": "layers/API/packages/api-rest/src", + "PoP\\ResourceLoader\\": "layers/SiteBuilder/packages/resourceloader/src", + "PoP\\Resources\\": "layers/SiteBuilder/packages/resources/src", + "PoP\\Root\\": "layers/Engine/packages/root/src", + "PoP\\RoutingWP\\": "layers/Engine/packages/routing-wp/src", + "PoP\\Routing\\": "layers/Engine/packages/routing/src", + "PoP\\SPA\\": "layers/SiteBuilder/packages/spa/src", + "PoP\\SSG\\": "layers/SiteBuilder/packages/static-site-generator/src", + "PoP\\SiteWP\\": "layers/SiteBuilder/packages/site-wp/src", + "PoP\\Site\\": "layers/SiteBuilder/packages/site/src", + "PoP\\TraceTools\\": "layers/Engine/packages/trace-tools/src", + "PoP\\TranslationWP\\": "layers/Engine/packages/translation-wp/src", + "PoP\\Translation\\": "layers/Engine/packages/translation/src" + } + }, + "autoload-dev": { + "psr-4": { + "GraphQLAPI\\ConvertCaseDirectives\\": "layers/GraphQLAPIForWP/plugins/convert-case-directives/tests", + "GraphQLAPI\\GraphQLAPI\\": "layers/GraphQLAPIForWP/plugins/graphql-api-for-wp/tests", + "GraphQLAPI\\SchemaFeedback\\": "layers/GraphQLAPIForWP/plugins/schema-feedback/tests", + "GraphQLByPoP\\GraphQLClientsForWP\\": "layers/GraphQLByPoP/packages/graphql-clients-for-wp/tests", + "GraphQLByPoP\\GraphQLEndpointForWP\\": "layers/GraphQLByPoP/packages/graphql-endpoint-for-wp/tests", + "GraphQLByPoP\\GraphQLParser\\": "layers/GraphQLByPoP/packages/graphql-parser/tests", + "GraphQLByPoP\\GraphQLQuery\\": "layers/GraphQLByPoP/packages/graphql-query/tests", + "GraphQLByPoP\\GraphQLRequest\\": "layers/GraphQLByPoP/packages/graphql-request/tests", + "GraphQLByPoP\\GraphQLServer\\": "layers/GraphQLByPoP/packages/graphql-server/tests", + "Leoloso\\ExamplesForPoP\\": "layers/Misc/packages/examples-for-pop/tests", + "PoPSchema\\BasicDirectives\\": "layers/Schema/packages/basic-directives/tests", + "PoPSchema\\BlockMetadataWP\\": "layers/Schema/packages/block-metadata-for-wp/tests", + "PoPSchema\\CDNDirective\\": "layers/Schema/packages/cdn-directive/tests", + "PoPSchema\\CategoriesWP\\": "layers/Schema/packages/categories-wp/tests", + "PoPSchema\\Categories\\": "layers/Schema/packages/categories/tests", + "PoPSchema\\CommentMetaWP\\": "layers/Schema/packages/commentmeta-wp/tests", + "PoPSchema\\CommentMeta\\": "layers/Schema/packages/commentmeta/tests", + "PoPSchema\\CommentMutationsWP\\": "layers/Schema/packages/comment-mutations-wp/tests", + "PoPSchema\\CommentMutations\\": "layers/Schema/packages/comment-mutations/tests", + "PoPSchema\\CommentsWP\\": "layers/Schema/packages/comments-wp/tests", + "PoPSchema\\Comments\\": "layers/Schema/packages/comments/tests", + "PoPSchema\\ConvertCaseDirectives\\": "layers/Schema/packages/convert-case-directives/tests", + "PoPSchema\\CustomPostMediaMutationsWP\\": "layers/Schema/packages/custompostmedia-mutations-wp/tests", + "PoPSchema\\CustomPostMediaMutations\\": "layers/Schema/packages/custompostmedia-mutations/tests", + "PoPSchema\\CustomPostMediaWP\\": "layers/Schema/packages/custompostmedia-wp/tests", + "PoPSchema\\CustomPostMedia\\": "layers/Schema/packages/custompostmedia/tests", + "PoPSchema\\CustomPostMetaWP\\": "layers/Schema/packages/custompostmeta-wp/tests", + "PoPSchema\\CustomPostMeta\\": "layers/Schema/packages/custompostmeta/tests", + "PoPSchema\\CustomPostMutationsWP\\": "layers/Schema/packages/custompost-mutations-wp/tests", + "PoPSchema\\CustomPostMutations\\": "layers/Schema/packages/custompost-mutations/tests", + "PoPSchema\\CustomPostsWP\\": "layers/Schema/packages/customposts-wp/tests", + "PoPSchema\\CustomPosts\\": "layers/Schema/packages/customposts/tests", + "PoPSchema\\EventMutationsWPEM\\": "layers/Schema/packages/event-mutations-wp-em/tests", + "PoPSchema\\EventMutations\\": "layers/Schema/packages/event-mutations/tests", + "PoPSchema\\EventsWPEM\\": "layers/Schema/packages/events-wp-em/tests", + "PoPSchema\\Events\\": "layers/Schema/packages/events/tests", + "PoPSchema\\EverythingElseWP\\": "layers/Schema/packages/everythingelse-wp/tests", + "PoPSchema\\EverythingElse\\": "layers/Schema/packages/everythingelse/tests", + "PoPSchema\\GenericCustomPosts\\": "layers/Schema/packages/generic-customposts/tests", + "PoPSchema\\GoogleTranslateDirectiveForCustomPosts\\": "layers/Schema/packages/google-translate-directive-for-customposts/tests", + "PoPSchema\\GoogleTranslateDirective\\": "layers/Schema/packages/google-translate-directive/tests", + "PoPSchema\\HighlightsWP\\": "layers/Schema/packages/highlights-wp/tests", + "PoPSchema\\Highlights\\": "layers/Schema/packages/highlights/tests", + "PoPSchema\\LocationPostsWP\\": "layers/Schema/packages/locationposts-wp/tests", + "PoPSchema\\LocationPosts\\": "layers/Schema/packages/locationposts/tests", + "PoPSchema\\LocationsWPEM\\": "layers/Schema/packages/locations-wp-em/tests", + "PoPSchema\\Locations\\": "layers/Schema/packages/locations/tests", + "PoPSchema\\MediaWP\\": "layers/Schema/packages/media-wp/tests", + "PoPSchema\\Media\\": "layers/Schema/packages/media/tests", + "PoPSchema\\MenusWP\\": "layers/Schema/packages/menus-wp/tests", + "PoPSchema\\Menus\\": "layers/Schema/packages/menus/tests", + "PoPSchema\\MetaQueryWP\\": "layers/Schema/packages/metaquery-wp/tests", + "PoPSchema\\MetaQuery\\": "layers/Schema/packages/metaquery/tests", + "PoPSchema\\Meta\\": "layers/Schema/packages/meta/tests", + "PoPSchema\\NotificationsWP\\": "layers/Schema/packages/notifications-wp/tests", + "PoPSchema\\Notifications\\": "layers/Schema/packages/notifications/tests", + "PoPSchema\\PagesWP\\": "layers/Schema/packages/pages-wp/tests", + "PoPSchema\\Pages\\": "layers/Schema/packages/pages/tests", + "PoPSchema\\PostMutations\\": "layers/Schema/packages/post-mutations/tests", + "PoPSchema\\PostTagsWP\\": "layers/Schema/packages/post-tags-wp/tests", + "PoPSchema\\PostTags\\": "layers/Schema/packages/post-tags/tests", + "PoPSchema\\PostsWP\\": "layers/Schema/packages/posts-wp/tests", + "PoPSchema\\Posts\\": "layers/Schema/packages/posts/tests", + "PoPSchema\\QueriedObjectWP\\": "layers/Schema/packages/queriedobject-wp/tests", + "PoPSchema\\QueriedObject\\": "layers/Schema/packages/queriedobject/tests", + "PoPSchema\\SchemaCommons\\": "layers/Schema/packages/schema-commons/tests", + "PoPSchema\\StancesWP\\": "layers/Schema/packages/stances-wp/tests", + "PoPSchema\\Stances\\": "layers/Schema/packages/stances/tests", + "PoPSchema\\TagsWP\\": "layers/Schema/packages/tags-wp/tests", + "PoPSchema\\Tags\\": "layers/Schema/packages/tags/tests", + "PoPSchema\\TaxonomiesWP\\": "layers/Schema/packages/taxonomies-wp/tests", + "PoPSchema\\Taxonomies\\": "layers/Schema/packages/taxonomies/tests", + "PoPSchema\\TaxonomyMetaWP\\": "layers/Schema/packages/taxonomymeta-wp/tests", + "PoPSchema\\TaxonomyMeta\\": "layers/Schema/packages/taxonomymeta/tests", + "PoPSchema\\TaxonomyQueryWP\\": "layers/Schema/packages/taxonomyquery-wp/tests", + "PoPSchema\\TaxonomyQuery\\": "layers/Schema/packages/taxonomyquery/tests", + "PoPSchema\\TranslateDirectiveACL\\": "layers/Schema/packages/translate-directive-acl/tests", + "PoPSchema\\TranslateDirective\\": "layers/Schema/packages/translate-directive/tests", + "PoPSchema\\UserMetaWP\\": "layers/Schema/packages/usermeta-wp/tests", + "PoPSchema\\UserMeta\\": "layers/Schema/packages/usermeta/tests", + "PoPSchema\\UserRolesACL\\": "layers/Schema/packages/user-roles-acl/tests", + "PoPSchema\\UserRolesAccessControl\\": "layers/Schema/packages/user-roles-access-control/tests", + "PoPSchema\\UserRolesWP\\": "layers/Schema/packages/user-roles-wp/tests", + "PoPSchema\\UserRoles\\": "layers/Schema/packages/user-roles/tests", + "PoPSchema\\UserStateAccessControl\\": "layers/Schema/packages/user-state-access-control/tests", + "PoPSchema\\UserStateMutationsWP\\": "layers/Schema/packages/user-state-mutations-wp/tests", + "PoPSchema\\UserStateMutations\\": "layers/Schema/packages/user-state-mutations/tests", + "PoPSchema\\UserStateWP\\": "layers/Schema/packages/user-state-wp/tests", + "PoPSchema\\UserState\\": "layers/Schema/packages/user-state/tests", + "PoPSchema\\UsersWP\\": "layers/Schema/packages/users-wp/tests", + "PoPSchema\\Users\\": "layers/Schema/packages/users/tests", + "PoPSitesWassup\\CommentMutations\\": "layers/Wassup/packages/comment-mutations/tests", + "PoPSitesWassup\\ContactUsMutations\\": "layers/Wassup/packages/contactus-mutations/tests", + "PoPSitesWassup\\ContactUserMutations\\": "layers/Wassup/packages/contactuser-mutations/tests", + "PoPSitesWassup\\CustomPostLinkMutations\\": "layers/Wassup/packages/custompostlink-mutations/tests", + "PoPSitesWassup\\CustomPostMutations\\": "layers/Wassup/packages/custompost-mutations/tests", + "PoPSitesWassup\\EventLinkMutations\\": "layers/Wassup/packages/eventlink-mutations/tests", + "PoPSitesWassup\\EventMutations\\": "layers/Wassup/packages/event-mutations/tests", + "PoPSitesWassup\\EverythingElseMutations\\": "layers/Wassup/packages/everythingelse-mutations/tests", + "PoPSitesWassup\\FlagMutations\\": "layers/Wassup/packages/flag-mutations/tests", + "PoPSitesWassup\\FormMutations\\": "layers/Wassup/packages/form-mutations/tests", + "PoPSitesWassup\\GravityFormsMutations\\": "layers/Wassup/packages/gravityforms-mutations/tests", + "PoPSitesWassup\\HighlightMutations\\": "layers/Wassup/packages/highlight-mutations/tests", + "PoPSitesWassup\\LocationMutations\\": "layers/Wassup/packages/location-mutations/tests", + "PoPSitesWassup\\LocationPostLinkMutations\\": "layers/Wassup/packages/locationpostlink-mutations/tests", + "PoPSitesWassup\\LocationPostMutations\\": "layers/Wassup/packages/locationpost-mutations/tests", + "PoPSitesWassup\\NewsletterMutations\\": "layers/Wassup/packages/newsletter-mutations/tests", + "PoPSitesWassup\\NotificationMutations\\": "layers/Wassup/packages/notification-mutations/tests", + "PoPSitesWassup\\PostLinkMutations\\": "layers/Wassup/packages/postlink-mutations/tests", + "PoPSitesWassup\\PostMutations\\": "layers/Wassup/packages/post-mutations/tests", + "PoPSitesWassup\\ShareMutations\\": "layers/Wassup/packages/share-mutations/tests", + "PoPSitesWassup\\SocialNetworkMutations\\": "layers/Wassup/packages/socialnetwork-mutations/tests", + "PoPSitesWassup\\StanceMutations\\": "layers/Wassup/packages/stance-mutations/tests", + "PoPSitesWassup\\SystemMutations\\": "layers/Wassup/packages/system-mutations/tests", + "PoPSitesWassup\\UserStateMutations\\": "layers/Wassup/packages/user-state-mutations/tests", + "PoPSitesWassup\\VolunteerMutations\\": "layers/Wassup/packages/volunteer-mutations/tests", + "PoPSitesWassup\\Wassup\\": "layers/Wassup/packages/wassup/tests", + "PoP\\APIClients\\": "layers/API/packages/api-clients/tests", + "PoP\\APIEndpointsForWP\\": "layers/API/packages/api-endpoints-for-wp/tests", + "PoP\\APIEndpoints\\": "layers/API/packages/api-endpoints/tests", + "PoP\\APIMirrorQuery\\": "layers/API/packages/api-mirrorquery/tests", + "PoP\\API\\": "layers/API/packages/api/tests", + "PoP\\AccessControl\\": "layers/Engine/packages/access-control/tests", + "PoP\\ApplicationWP\\": "layers/SiteBuilder/packages/application-wp/tests", + "PoP\\Application\\": "layers/SiteBuilder/packages/application/tests", + "PoP\\Base36Definitions\\": "layers/SiteBuilder/packages/definitions-base36/tests", + "PoP\\CacheControl\\": "layers/Engine/packages/cache-control/tests", + "PoP\\ComponentModel\\": "layers/Engine/packages/component-model/tests", + "PoP\\ConfigurableSchemaFeedback\\": "layers/Engine/packages/configurable-schema-feedback/tests", + "PoP\\ConfigurationComponentModel\\": "layers/SiteBuilder/packages/component-model-configuration/tests", + "PoP\\DefinitionPersistence\\": "layers/SiteBuilder/packages/definitionpersistence/tests", + "PoP\\Definitions\\": "layers/Engine/packages/definitions/tests", + "PoP\\EmojiDefinitions\\": "layers/SiteBuilder/packages/definitions-emoji/tests", + "PoP\\EngineWP\\": "layers/Engine/packages/engine-wp/tests", + "PoP\\Engine\\": "layers/Engine/packages/engine/tests", + "PoP\\FieldQuery\\": "layers/Engine/packages/field-query/tests", + "PoP\\FileStore\\": "layers/Engine/packages/filestore/tests", + "PoP\\FunctionFields\\": "layers/Engine/packages/function-fields/tests", + "PoP\\GraphQLAPI\\": "layers/API/packages/api-graphql/tests", + "PoP\\GuzzleHelpers\\": "layers/Engine/packages/guzzle-helpers/tests", + "PoP\\HooksWP\\": "layers/Engine/packages/hooks-wp/tests", + "PoP\\Hooks\\": "layers/Engine/packages/hooks/tests", + "PoP\\LooseContracts\\": "layers/Engine/packages/loosecontracts/tests", + "PoP\\MandatoryDirectivesByConfiguration\\": "layers/Engine/packages/mandatory-directives-by-configuration/tests", + "PoP\\ModuleRouting\\": "layers/Engine/packages/modulerouting/tests", + "PoP\\Multisite\\": "layers/SiteBuilder/packages/multisite/tests", + "PoP\\QueryParsing\\": "layers/Engine/packages/query-parsing/tests", + "PoP\\RESTAPI\\": "layers/API/packages/api-rest/tests", + "PoP\\ResourceLoader\\": "layers/SiteBuilder/packages/resourceloader/tests", + "PoP\\Resources\\": "layers/SiteBuilder/packages/resources/tests", + "PoP\\Root\\": "layers/Engine/packages/root/tests", + "PoP\\RoutingWP\\": "layers/Engine/packages/routing-wp/tests", + "PoP\\Routing\\": "layers/Engine/packages/routing/tests", + "PoP\\SPA\\": "layers/SiteBuilder/packages/spa/tests", + "PoP\\SSG\\": "layers/SiteBuilder/packages/static-site-generator/tests", + "PoP\\SiteWP\\": "layers/SiteBuilder/packages/site-wp/tests", + "PoP\\Site\\": "layers/SiteBuilder/packages/site/tests", + "PoP\\TraceTools\\": "layers/Engine/packages/trace-tools/tests", + "PoP\\TranslationWP\\": "layers/Engine/packages/translation-wp/tests", + "PoP\\Translation\\": "layers/Engine/packages/translation/tests" + } + }, + "extra": { + "wordpress-install-dir": "vendor/wordpress/wordpress", + "merge-plugin": { + "include": [ + "composer.local.json" + ], + "recurse": true, + "replace": false, + "ignore-duplicates": false, + "merge-dev": true, + "merge-extra": false, + "merge-extra-deep": false, + "merge-scripts": false + } + }, + "replace": { + "getpop/access-control": "self.version", + "getpop/api": "self.version", + "getpop/api-clients": "self.version", + "getpop/api-endpoints": "self.version", + "getpop/api-endpoints-for-wp": "self.version", + "getpop/api-graphql": "self.version", + "getpop/api-mirrorquery": "self.version", + "getpop/api-rest": "self.version", + "getpop/application": "self.version", + "getpop/application-wp": "self.version", + "getpop/cache-control": "self.version", + "getpop/component-model": "self.version", + "getpop/component-model-configuration": "self.version", + "getpop/configurable-schema-feedback": "self.version", + "getpop/definitionpersistence": "self.version", + "getpop/definitions": "self.version", + "getpop/definitions-base36": "self.version", + "getpop/definitions-emoji": "self.version", + "getpop/engine": "self.version", + "getpop/engine-wp": "self.version", + "getpop/engine-wp-bootloader": "self.version", + "getpop/field-query": "self.version", + "getpop/filestore": "self.version", + "getpop/function-fields": "self.version", + "getpop/guzzle-helpers": "self.version", + "getpop/hooks": "self.version", + "getpop/hooks-wp": "self.version", + "getpop/loosecontracts": "self.version", + "getpop/mandatory-directives-by-configuration": "self.version", + "getpop/migrate-api": "self.version", + "getpop/migrate-api-graphql": "self.version", + "getpop/migrate-component-model": "self.version", + "getpop/migrate-component-model-configuration": "self.version", + "getpop/migrate-engine": "self.version", + "getpop/migrate-engine-wp": "self.version", + "getpop/migrate-static-site-generator": "self.version", + "getpop/modulerouting": "self.version", + "getpop/multisite": "self.version", + "getpop/query-parsing": "self.version", + "getpop/resourceloader": "self.version", + "getpop/resources": "self.version", + "getpop/root": "self.version", + "getpop/routing": "self.version", + "getpop/routing-wp": "self.version", + "getpop/site": "self.version", + "getpop/site-wp": "self.version", + "getpop/spa": "self.version", + "getpop/static-site-generator": "self.version", + "getpop/trace-tools": "self.version", + "getpop/translation": "self.version", + "getpop/translation-wp": "self.version", + "graphql-api/convert-case-directives": "self.version", + "graphql-api/graphql-api-for-wp": "self.version", + "graphql-api/schema-feedback": "self.version", + "graphql-by-pop/graphql-clients-for-wp": "self.version", + "graphql-by-pop/graphql-endpoint-for-wp": "self.version", + "graphql-by-pop/graphql-parser": "self.version", + "graphql-by-pop/graphql-query": "self.version", + "graphql-by-pop/graphql-request": "self.version", + "graphql-by-pop/graphql-server": "self.version", + "leoloso/examples-for-pop": "self.version", + "pop-migrate-everythingelse/cssconverter": "self.version", + "pop-migrate-everythingelse/ssr": "self.version", + "pop-schema/basic-directives": "self.version", + "pop-schema/block-metadata-for-wp": "self.version", + "pop-schema/categories": "self.version", + "pop-schema/categories-wp": "self.version", + "pop-schema/cdn-directive": "self.version", + "pop-schema/comment-mutations": "self.version", + "pop-schema/comment-mutations-wp": "self.version", + "pop-schema/commentmeta": "self.version", + "pop-schema/commentmeta-wp": "self.version", + "pop-schema/comments": "self.version", + "pop-schema/comments-wp": "self.version", + "pop-schema/convert-case-directives": "self.version", + "pop-schema/custompost-mutations": "self.version", + "pop-schema/custompost-mutations-wp": "self.version", + "pop-schema/custompostmedia": "self.version", + "pop-schema/custompostmedia-mutations": "self.version", + "pop-schema/custompostmedia-mutations-wp": "self.version", + "pop-schema/custompostmedia-wp": "self.version", + "pop-schema/custompostmeta": "self.version", + "pop-schema/custompostmeta-wp": "self.version", + "pop-schema/customposts": "self.version", + "pop-schema/customposts-wp": "self.version", + "pop-schema/event-mutations": "self.version", + "pop-schema/event-mutations-wp-em": "self.version", + "pop-schema/events": "self.version", + "pop-schema/events-wp-em": "self.version", + "pop-schema/everythingelse": "self.version", + "pop-schema/everythingelse-wp": "self.version", + "pop-schema/generic-customposts": "self.version", + "pop-schema/google-translate-directive": "self.version", + "pop-schema/google-translate-directive-for-customposts": "self.version", + "pop-schema/highlights": "self.version", + "pop-schema/highlights-wp": "self.version", + "pop-schema/locationposts": "self.version", + "pop-schema/locationposts-wp": "self.version", + "pop-schema/locations": "self.version", + "pop-schema/locations-wp-em": "self.version", + "pop-schema/media": "self.version", + "pop-schema/media-wp": "self.version", + "pop-schema/menus": "self.version", + "pop-schema/menus-wp": "self.version", + "pop-schema/meta": "self.version", + "pop-schema/metaquery": "self.version", + "pop-schema/metaquery-wp": "self.version", + "pop-schema/migrate-categories": "self.version", + "pop-schema/migrate-categories-wp": "self.version", + "pop-schema/migrate-commentmeta": "self.version", + "pop-schema/migrate-commentmeta-wp": "self.version", + "pop-schema/migrate-comments": "self.version", + "pop-schema/migrate-comments-wp": "self.version", + "pop-schema/migrate-custompostmedia": "self.version", + "pop-schema/migrate-custompostmedia-wp": "self.version", + "pop-schema/migrate-custompostmeta": "self.version", + "pop-schema/migrate-custompostmeta-wp": "self.version", + "pop-schema/migrate-customposts": "self.version", + "pop-schema/migrate-customposts-wp": "self.version", + "pop-schema/migrate-events": "self.version", + "pop-schema/migrate-events-wp-em": "self.version", + "pop-schema/migrate-everythingelse": "self.version", + "pop-schema/migrate-locations": "self.version", + "pop-schema/migrate-locations-wp-em": "self.version", + "pop-schema/migrate-media": "self.version", + "pop-schema/migrate-media-wp": "self.version", + "pop-schema/migrate-meta": "self.version", + "pop-schema/migrate-metaquery": "self.version", + "pop-schema/migrate-metaquery-wp": "self.version", + "pop-schema/migrate-pages": "self.version", + "pop-schema/migrate-pages-wp": "self.version", + "pop-schema/migrate-post-tags": "self.version", + "pop-schema/migrate-post-tags-wp": "self.version", + "pop-schema/migrate-posts": "self.version", + "pop-schema/migrate-posts-wp": "self.version", + "pop-schema/migrate-queriedobject": "self.version", + "pop-schema/migrate-queriedobject-wp": "self.version", + "pop-schema/migrate-tags": "self.version", + "pop-schema/migrate-tags-wp": "self.version", + "pop-schema/migrate-taxonomies": "self.version", + "pop-schema/migrate-taxonomies-wp": "self.version", + "pop-schema/migrate-taxonomymeta": "self.version", + "pop-schema/migrate-taxonomymeta-wp": "self.version", + "pop-schema/migrate-taxonomyquery": "self.version", + "pop-schema/migrate-taxonomyquery-wp": "self.version", + "pop-schema/migrate-usermeta": "self.version", + "pop-schema/migrate-usermeta-wp": "self.version", + "pop-schema/migrate-users": "self.version", + "pop-schema/migrate-users-wp": "self.version", + "pop-schema/notifications": "self.version", + "pop-schema/notifications-wp": "self.version", + "pop-schema/pages": "self.version", + "pop-schema/pages-wp": "self.version", + "pop-schema/post-mutations": "self.version", + "pop-schema/post-tags": "self.version", + "pop-schema/post-tags-wp": "self.version", + "pop-schema/posts": "self.version", + "pop-schema/posts-wp": "self.version", + "pop-schema/queriedobject": "self.version", + "pop-schema/queriedobject-wp": "self.version", + "pop-schema/schema-commons": "self.version", + "pop-schema/stances": "self.version", + "pop-schema/stances-wp": "self.version", + "pop-schema/tags": "self.version", + "pop-schema/tags-wp": "self.version", + "pop-schema/taxonomies": "self.version", + "pop-schema/taxonomies-wp": "self.version", + "pop-schema/taxonomymeta": "self.version", + "pop-schema/taxonomymeta-wp": "self.version", + "pop-schema/taxonomyquery": "self.version", + "pop-schema/taxonomyquery-wp": "self.version", + "pop-schema/translate-directive": "self.version", + "pop-schema/translate-directive-acl": "self.version", + "pop-schema/user-roles": "self.version", + "pop-schema/user-roles-access-control": "self.version", + "pop-schema/user-roles-acl": "self.version", + "pop-schema/user-roles-wp": "self.version", + "pop-schema/user-state": "self.version", + "pop-schema/user-state-access-control": "self.version", + "pop-schema/user-state-mutations": "self.version", + "pop-schema/user-state-mutations-wp": "self.version", + "pop-schema/user-state-wp": "self.version", + "pop-schema/usermeta": "self.version", + "pop-schema/usermeta-wp": "self.version", + "pop-schema/users": "self.version", + "pop-schema/users-wp": "self.version", + "pop-sites-wassup/comment-mutations": "self.version", + "pop-sites-wassup/contactus-mutations": "self.version", + "pop-sites-wassup/contactuser-mutations": "self.version", + "pop-sites-wassup/custompost-mutations": "self.version", + "pop-sites-wassup/custompostlink-mutations": "self.version", + "pop-sites-wassup/event-mutations": "self.version", + "pop-sites-wassup/eventlink-mutations": "self.version", + "pop-sites-wassup/everythingelse-mutations": "self.version", + "pop-sites-wassup/flag-mutations": "self.version", + "pop-sites-wassup/form-mutations": "self.version", + "pop-sites-wassup/gravityforms-mutations": "self.version", + "pop-sites-wassup/highlight-mutations": "self.version", + "pop-sites-wassup/location-mutations": "self.version", + "pop-sites-wassup/locationpost-mutations": "self.version", + "pop-sites-wassup/locationpostlink-mutations": "self.version", + "pop-sites-wassup/newsletter-mutations": "self.version", + "pop-sites-wassup/notification-mutations": "self.version", + "pop-sites-wassup/post-mutations": "self.version", + "pop-sites-wassup/postlink-mutations": "self.version", + "pop-sites-wassup/share-mutations": "self.version", + "pop-sites-wassup/socialnetwork-mutations": "self.version", + "pop-sites-wassup/stance-mutations": "self.version", + "pop-sites-wassup/system-mutations": "self.version", + "pop-sites-wassup/user-state-mutations": "self.version", + "pop-sites-wassup/volunteer-mutations": "self.version", + "pop-sites-wassup/wassup": "self.version" + }, + "authors": [ + { + "name": "Leonardo Losoviz", + "email": "leo@getpop.org", + "homepage": "https://getpop.org" + } + ], + "description": "Monorepo for all the PoP packages", + "license": "GPL-2.0-or-later", + "config": { + "sort-packages": true + }, + "repositories": [ + { + "type": "composer", + "url": "https://wpackagist.org" + }, + { + "type": "vcs", + "url": "https://github.com/leoloso/wp-muplugin-loader.git" + }, + { + "type": "vcs", + "url": "https://github.com/mcaskill/composer-merge-plugin.git" + } + ], + "scripts": { + "test": "phpunit", + "check-style": "phpcs -n src $(monorepo-builder source-packages --subfolder=src --subfolder=tests)", + "fix-style": "phpcbf -n src $(monorepo-builder source-packages --subfolder=src --subfolder=tests)", + "analyse": "ci/phpstan.sh \". $(monorepo-builder source-packages --skip-unmigrated)\"", + "preview-src-downgrade": "rector process $(monorepo-builder source-packages --subfolder=src) --config=rector-downgrade-code.php --ansi --dry-run || true", + "preview-vendor-downgrade": "layers/Engine/packages/root/ci/downgrade_code.sh 7.1 rector-downgrade-code.php --dry-run || true", + "preview-code-downgrade": [ + "@preview-src-downgrade", + "@preview-vendor-downgrade" + ], + "build-server": [ + "lando init --source remote --remote-url https://wordpress.org/latest.tar.gz --recipe wordpress --webroot wordpress --name graphql-api-dev", + "@start-server" + ], + "start-server": [ + "cd layers/GraphQLAPIForWP/plugins/graphql-api-for-wp && composer install", + "lando start" + ], + "rebuild-server": "lando rebuild -y", + "merge-monorepo": "monorepo-builder merge --ansi", + "propagate-monorepo": "monorepo-builder propagate --ansi", + "validate-monorepo": "monorepo-builder validate --ansi", + "release": "monorepo-builder release patch --ansi" + }, + "minimum-stability": "dev", + "prefer-stable": true +}"# + .to_string(), + ) + .unwrap(); + + assert!( + manipulator + .add_sub_node("config", "platform-check", PhpMixed::Bool(false), true) + .unwrap() + ); + assert_eq!( + r#"{ + "name": "leoloso/pop", + "require": { + "php": "^7.4|^8.0", + "ext-mbstring": "*", + "brain/cortex": "~1.0.0", + "composer/installers": "~1.0", + "composer/semver": "^1.5", + "erusev/parsedown": "^1.7", + "guzzlehttp/guzzle": "~6.3", + "jrfnl/php-cast-to-type": "^2.0", + "league/pipeline": "^1.0", + "lkwdwrd/wp-muplugin-loader": "dev-feature-composer-v2", + "obsidian/polyfill-hrtime": "^0.1", + "psr/cache": "^1.0", + "symfony/cache": "^5.1", + "symfony/config": "^5.1", + "symfony/dependency-injection": "^5.1", + "symfony/dotenv": "^5.1", + "symfony/expression-language": "^5.1", + "symfony/polyfill-php72": "^1.18", + "symfony/polyfill-php73": "^1.18", + "symfony/polyfill-php74": "^1.18", + "symfony/polyfill-php80": "^1.18", + "symfony/property-access": "^5.1", + "symfony/yaml": "^5.1" + }, + "require-dev": { + "johnpbloch/wordpress": ">=5.5", + "phpstan/phpstan": "^0.12", + "phpunit/phpunit": ">=9.3", + "rector/rector": "^0.9", + "squizlabs/php_codesniffer": "^3.0", + "symfony/var-dumper": "^5.1", + "symplify/monorepo-builder": "^9.0", + "szepeviktor/phpstan-wordpress": "^0.6.2" + }, + "autoload": { + "psr-4": { + "GraphQLAPI\\ConvertCaseDirectives\\": "layers/GraphQLAPIForWP/plugins/convert-case-directives/src", + "GraphQLAPI\\GraphQLAPI\\": "layers/GraphQLAPIForWP/plugins/graphql-api-for-wp/src", + "GraphQLAPI\\SchemaFeedback\\": "layers/GraphQLAPIForWP/plugins/schema-feedback/src", + "GraphQLByPoP\\GraphQLClientsForWP\\": "layers/GraphQLByPoP/packages/graphql-clients-for-wp/src", + "GraphQLByPoP\\GraphQLEndpointForWP\\": "layers/GraphQLByPoP/packages/graphql-endpoint-for-wp/src", + "GraphQLByPoP\\GraphQLParser\\": "layers/GraphQLByPoP/packages/graphql-parser/src", + "GraphQLByPoP\\GraphQLQuery\\": "layers/GraphQLByPoP/packages/graphql-query/src", + "GraphQLByPoP\\GraphQLRequest\\": "layers/GraphQLByPoP/packages/graphql-request/src", + "GraphQLByPoP\\GraphQLServer\\": "layers/GraphQLByPoP/packages/graphql-server/src", + "Leoloso\\ExamplesForPoP\\": "layers/Misc/packages/examples-for-pop/src", + "PoPSchema\\BasicDirectives\\": "layers/Schema/packages/basic-directives/src", + "PoPSchema\\BlockMetadataWP\\": "layers/Schema/packages/block-metadata-for-wp/src", + "PoPSchema\\CDNDirective\\": "layers/Schema/packages/cdn-directive/src", + "PoPSchema\\CategoriesWP\\": "layers/Schema/packages/categories-wp/src", + "PoPSchema\\Categories\\": "layers/Schema/packages/categories/src", + "PoPSchema\\CommentMetaWP\\": "layers/Schema/packages/commentmeta-wp/src", + "PoPSchema\\CommentMeta\\": "layers/Schema/packages/commentmeta/src", + "PoPSchema\\CommentMutationsWP\\": "layers/Schema/packages/comment-mutations-wp/src", + "PoPSchema\\CommentMutations\\": "layers/Schema/packages/comment-mutations/src", + "PoPSchema\\CommentsWP\\": "layers/Schema/packages/comments-wp/src", + "PoPSchema\\Comments\\": "layers/Schema/packages/comments/src", + "PoPSchema\\ConvertCaseDirectives\\": "layers/Schema/packages/convert-case-directives/src", + "PoPSchema\\CustomPostMediaMutationsWP\\": "layers/Schema/packages/custompostmedia-mutations-wp/src", + "PoPSchema\\CustomPostMediaMutations\\": "layers/Schema/packages/custompostmedia-mutations/src", + "PoPSchema\\CustomPostMediaWP\\": "layers/Schema/packages/custompostmedia-wp/src", + "PoPSchema\\CustomPostMedia\\": "layers/Schema/packages/custompostmedia/src", + "PoPSchema\\CustomPostMetaWP\\": "layers/Schema/packages/custompostmeta-wp/src", + "PoPSchema\\CustomPostMeta\\": "layers/Schema/packages/custompostmeta/src", + "PoPSchema\\CustomPostMutationsWP\\": "layers/Schema/packages/custompost-mutations-wp/src", + "PoPSchema\\CustomPostMutations\\": "layers/Schema/packages/custompost-mutations/src", + "PoPSchema\\CustomPostsWP\\": "layers/Schema/packages/customposts-wp/src", + "PoPSchema\\CustomPosts\\": "layers/Schema/packages/customposts/src", + "PoPSchema\\EventMutationsWPEM\\": "layers/Schema/packages/event-mutations-wp-em/src", + "PoPSchema\\EventMutations\\": "layers/Schema/packages/event-mutations/src", + "PoPSchema\\EventsWPEM\\": "layers/Schema/packages/events-wp-em/src", + "PoPSchema\\Events\\": "layers/Schema/packages/events/src", + "PoPSchema\\EverythingElseWP\\": "layers/Schema/packages/everythingelse-wp/src", + "PoPSchema\\EverythingElse\\": "layers/Schema/packages/everythingelse/src", + "PoPSchema\\GenericCustomPosts\\": "layers/Schema/packages/generic-customposts/src", + "PoPSchema\\GoogleTranslateDirectiveForCustomPosts\\": "layers/Schema/packages/google-translate-directive-for-customposts/src", + "PoPSchema\\GoogleTranslateDirective\\": "layers/Schema/packages/google-translate-directive/src", + "PoPSchema\\HighlightsWP\\": "layers/Schema/packages/highlights-wp/src", + "PoPSchema\\Highlights\\": "layers/Schema/packages/highlights/src", + "PoPSchema\\LocationPostsWP\\": "layers/Schema/packages/locationposts-wp/src", + "PoPSchema\\LocationPosts\\": "layers/Schema/packages/locationposts/src", + "PoPSchema\\LocationsWPEM\\": "layers/Schema/packages/locations-wp-em/src", + "PoPSchema\\Locations\\": "layers/Schema/packages/locations/src", + "PoPSchema\\MediaWP\\": "layers/Schema/packages/media-wp/src", + "PoPSchema\\Media\\": "layers/Schema/packages/media/src", + "PoPSchema\\MenusWP\\": "layers/Schema/packages/menus-wp/src", + "PoPSchema\\Menus\\": "layers/Schema/packages/menus/src", + "PoPSchema\\MetaQueryWP\\": "layers/Schema/packages/metaquery-wp/src", + "PoPSchema\\MetaQuery\\": "layers/Schema/packages/metaquery/src", + "PoPSchema\\Meta\\": "layers/Schema/packages/meta/src", + "PoPSchema\\NotificationsWP\\": "layers/Schema/packages/notifications-wp/src", + "PoPSchema\\Notifications\\": "layers/Schema/packages/notifications/src", + "PoPSchema\\PagesWP\\": "layers/Schema/packages/pages-wp/src", + "PoPSchema\\Pages\\": "layers/Schema/packages/pages/src", + "PoPSchema\\PostMutations\\": "layers/Schema/packages/post-mutations/src", + "PoPSchema\\PostTagsWP\\": "layers/Schema/packages/post-tags-wp/src", + "PoPSchema\\PostTags\\": "layers/Schema/packages/post-tags/src", + "PoPSchema\\PostsWP\\": "layers/Schema/packages/posts-wp/src", + "PoPSchema\\Posts\\": "layers/Schema/packages/posts/src", + "PoPSchema\\QueriedObjectWP\\": "layers/Schema/packages/queriedobject-wp/src", + "PoPSchema\\QueriedObject\\": "layers/Schema/packages/queriedobject/src", + "PoPSchema\\SchemaCommons\\": "layers/Schema/packages/schema-commons/src", + "PoPSchema\\StancesWP\\": "layers/Schema/packages/stances-wp/src", + "PoPSchema\\Stances\\": "layers/Schema/packages/stances/src", + "PoPSchema\\TagsWP\\": "layers/Schema/packages/tags-wp/src", + "PoPSchema\\Tags\\": "layers/Schema/packages/tags/src", + "PoPSchema\\TaxonomiesWP\\": "layers/Schema/packages/taxonomies-wp/src", + "PoPSchema\\Taxonomies\\": "layers/Schema/packages/taxonomies/src", + "PoPSchema\\TaxonomyMetaWP\\": "layers/Schema/packages/taxonomymeta-wp/src", + "PoPSchema\\TaxonomyMeta\\": "layers/Schema/packages/taxonomymeta/src", + "PoPSchema\\TaxonomyQueryWP\\": "layers/Schema/packages/taxonomyquery-wp/src", + "PoPSchema\\TaxonomyQuery\\": "layers/Schema/packages/taxonomyquery/src", + "PoPSchema\\TranslateDirectiveACL\\": "layers/Schema/packages/translate-directive-acl/src", + "PoPSchema\\TranslateDirective\\": "layers/Schema/packages/translate-directive/src", + "PoPSchema\\UserMetaWP\\": "layers/Schema/packages/usermeta-wp/src", + "PoPSchema\\UserMeta\\": "layers/Schema/packages/usermeta/src", + "PoPSchema\\UserRolesACL\\": "layers/Schema/packages/user-roles-acl/src", + "PoPSchema\\UserRolesAccessControl\\": "layers/Schema/packages/user-roles-access-control/src", + "PoPSchema\\UserRolesWP\\": "layers/Schema/packages/user-roles-wp/src", + "PoPSchema\\UserRoles\\": "layers/Schema/packages/user-roles/src", + "PoPSchema\\UserStateAccessControl\\": "layers/Schema/packages/user-state-access-control/src", + "PoPSchema\\UserStateMutationsWP\\": "layers/Schema/packages/user-state-mutations-wp/src", + "PoPSchema\\UserStateMutations\\": "layers/Schema/packages/user-state-mutations/src", + "PoPSchema\\UserStateWP\\": "layers/Schema/packages/user-state-wp/src", + "PoPSchema\\UserState\\": "layers/Schema/packages/user-state/src", + "PoPSchema\\UsersWP\\": "layers/Schema/packages/users-wp/src", + "PoPSchema\\Users\\": "layers/Schema/packages/users/src", + "PoPSitesWassup\\CommentMutations\\": "layers/Wassup/packages/comment-mutations/src", + "PoPSitesWassup\\ContactUsMutations\\": "layers/Wassup/packages/contactus-mutations/src", + "PoPSitesWassup\\ContactUserMutations\\": "layers/Wassup/packages/contactuser-mutations/src", + "PoPSitesWassup\\CustomPostLinkMutations\\": "layers/Wassup/packages/custompostlink-mutations/src", + "PoPSitesWassup\\CustomPostMutations\\": "layers/Wassup/packages/custompost-mutations/src", + "PoPSitesWassup\\EventLinkMutations\\": "layers/Wassup/packages/eventlink-mutations/src", + "PoPSitesWassup\\EventMutations\\": "layers/Wassup/packages/event-mutations/src", + "PoPSitesWassup\\EverythingElseMutations\\": "layers/Wassup/packages/everythingelse-mutations/src", + "PoPSitesWassup\\FlagMutations\\": "layers/Wassup/packages/flag-mutations/src", + "PoPSitesWassup\\FormMutations\\": "layers/Wassup/packages/form-mutations/src", + "PoPSitesWassup\\GravityFormsMutations\\": "layers/Wassup/packages/gravityforms-mutations/src", + "PoPSitesWassup\\HighlightMutations\\": "layers/Wassup/packages/highlight-mutations/src", + "PoPSitesWassup\\LocationMutations\\": "layers/Wassup/packages/location-mutations/src", + "PoPSitesWassup\\LocationPostLinkMutations\\": "layers/Wassup/packages/locationpostlink-mutations/src", + "PoPSitesWassup\\LocationPostMutations\\": "layers/Wassup/packages/locationpost-mutations/src", + "PoPSitesWassup\\NewsletterMutations\\": "layers/Wassup/packages/newsletter-mutations/src", + "PoPSitesWassup\\NotificationMutations\\": "layers/Wassup/packages/notification-mutations/src", + "PoPSitesWassup\\PostLinkMutations\\": "layers/Wassup/packages/postlink-mutations/src", + "PoPSitesWassup\\PostMutations\\": "layers/Wassup/packages/post-mutations/src", + "PoPSitesWassup\\ShareMutations\\": "layers/Wassup/packages/share-mutations/src", + "PoPSitesWassup\\SocialNetworkMutations\\": "layers/Wassup/packages/socialnetwork-mutations/src", + "PoPSitesWassup\\StanceMutations\\": "layers/Wassup/packages/stance-mutations/src", + "PoPSitesWassup\\SystemMutations\\": "layers/Wassup/packages/system-mutations/src", + "PoPSitesWassup\\UserStateMutations\\": "layers/Wassup/packages/user-state-mutations/src", + "PoPSitesWassup\\VolunteerMutations\\": "layers/Wassup/packages/volunteer-mutations/src", + "PoPSitesWassup\\Wassup\\": "layers/Wassup/packages/wassup/src", + "PoP\\APIClients\\": "layers/API/packages/api-clients/src", + "PoP\\APIEndpointsForWP\\": "layers/API/packages/api-endpoints-for-wp/src", + "PoP\\APIEndpoints\\": "layers/API/packages/api-endpoints/src", + "PoP\\APIMirrorQuery\\": "layers/API/packages/api-mirrorquery/src", + "PoP\\API\\": "layers/API/packages/api/src", + "PoP\\AccessControl\\": "layers/Engine/packages/access-control/src", + "PoP\\ApplicationWP\\": "layers/SiteBuilder/packages/application-wp/src", + "PoP\\Application\\": "layers/SiteBuilder/packages/application/src", + "PoP\\Base36Definitions\\": "layers/SiteBuilder/packages/definitions-base36/src", + "PoP\\CacheControl\\": "layers/Engine/packages/cache-control/src", + "PoP\\ComponentModel\\": "layers/Engine/packages/component-model/src", + "PoP\\ConfigurableSchemaFeedback\\": "layers/Engine/packages/configurable-schema-feedback/src", + "PoP\\ConfigurationComponentModel\\": "layers/SiteBuilder/packages/component-model-configuration/src", + "PoP\\DefinitionPersistence\\": "layers/SiteBuilder/packages/definitionpersistence/src", + "PoP\\Definitions\\": "layers/Engine/packages/definitions/src", + "PoP\\EmojiDefinitions\\": "layers/SiteBuilder/packages/definitions-emoji/src", + "PoP\\EngineWP\\": "layers/Engine/packages/engine-wp/src", + "PoP\\Engine\\": "layers/Engine/packages/engine/src", + "PoP\\FieldQuery\\": "layers/Engine/packages/field-query/src", + "PoP\\FileStore\\": "layers/Engine/packages/filestore/src", + "PoP\\FunctionFields\\": "layers/Engine/packages/function-fields/src", + "PoP\\GraphQLAPI\\": "layers/API/packages/api-graphql/src", + "PoP\\GuzzleHelpers\\": "layers/Engine/packages/guzzle-helpers/src", + "PoP\\HooksWP\\": "layers/Engine/packages/hooks-wp/src", + "PoP\\Hooks\\": "layers/Engine/packages/hooks/src", + "PoP\\LooseContracts\\": "layers/Engine/packages/loosecontracts/src", + "PoP\\MandatoryDirectivesByConfiguration\\": "layers/Engine/packages/mandatory-directives-by-configuration/src", + "PoP\\ModuleRouting\\": "layers/Engine/packages/modulerouting/src", + "PoP\\Multisite\\": "layers/SiteBuilder/packages/multisite/src", + "PoP\\PoP\\": "src", + "PoP\\QueryParsing\\": "layers/Engine/packages/query-parsing/src", + "PoP\\RESTAPI\\": "layers/API/packages/api-rest/src", + "PoP\\ResourceLoader\\": "layers/SiteBuilder/packages/resourceloader/src", + "PoP\\Resources\\": "layers/SiteBuilder/packages/resources/src", + "PoP\\Root\\": "layers/Engine/packages/root/src", + "PoP\\RoutingWP\\": "layers/Engine/packages/routing-wp/src", + "PoP\\Routing\\": "layers/Engine/packages/routing/src", + "PoP\\SPA\\": "layers/SiteBuilder/packages/spa/src", + "PoP\\SSG\\": "layers/SiteBuilder/packages/static-site-generator/src", + "PoP\\SiteWP\\": "layers/SiteBuilder/packages/site-wp/src", + "PoP\\Site\\": "layers/SiteBuilder/packages/site/src", + "PoP\\TraceTools\\": "layers/Engine/packages/trace-tools/src", + "PoP\\TranslationWP\\": "layers/Engine/packages/translation-wp/src", + "PoP\\Translation\\": "layers/Engine/packages/translation/src" + } + }, + "autoload-dev": { + "psr-4": { + "GraphQLAPI\\ConvertCaseDirectives\\": "layers/GraphQLAPIForWP/plugins/convert-case-directives/tests", + "GraphQLAPI\\GraphQLAPI\\": "layers/GraphQLAPIForWP/plugins/graphql-api-for-wp/tests", + "GraphQLAPI\\SchemaFeedback\\": "layers/GraphQLAPIForWP/plugins/schema-feedback/tests", + "GraphQLByPoP\\GraphQLClientsForWP\\": "layers/GraphQLByPoP/packages/graphql-clients-for-wp/tests", + "GraphQLByPoP\\GraphQLEndpointForWP\\": "layers/GraphQLByPoP/packages/graphql-endpoint-for-wp/tests", + "GraphQLByPoP\\GraphQLParser\\": "layers/GraphQLByPoP/packages/graphql-parser/tests", + "GraphQLByPoP\\GraphQLQuery\\": "layers/GraphQLByPoP/packages/graphql-query/tests", + "GraphQLByPoP\\GraphQLRequest\\": "layers/GraphQLByPoP/packages/graphql-request/tests", + "GraphQLByPoP\\GraphQLServer\\": "layers/GraphQLByPoP/packages/graphql-server/tests", + "Leoloso\\ExamplesForPoP\\": "layers/Misc/packages/examples-for-pop/tests", + "PoPSchema\\BasicDirectives\\": "layers/Schema/packages/basic-directives/tests", + "PoPSchema\\BlockMetadataWP\\": "layers/Schema/packages/block-metadata-for-wp/tests", + "PoPSchema\\CDNDirective\\": "layers/Schema/packages/cdn-directive/tests", + "PoPSchema\\CategoriesWP\\": "layers/Schema/packages/categories-wp/tests", + "PoPSchema\\Categories\\": "layers/Schema/packages/categories/tests", + "PoPSchema\\CommentMetaWP\\": "layers/Schema/packages/commentmeta-wp/tests", + "PoPSchema\\CommentMeta\\": "layers/Schema/packages/commentmeta/tests", + "PoPSchema\\CommentMutationsWP\\": "layers/Schema/packages/comment-mutations-wp/tests", + "PoPSchema\\CommentMutations\\": "layers/Schema/packages/comment-mutations/tests", + "PoPSchema\\CommentsWP\\": "layers/Schema/packages/comments-wp/tests", + "PoPSchema\\Comments\\": "layers/Schema/packages/comments/tests", + "PoPSchema\\ConvertCaseDirectives\\": "layers/Schema/packages/convert-case-directives/tests", + "PoPSchema\\CustomPostMediaMutationsWP\\": "layers/Schema/packages/custompostmedia-mutations-wp/tests", + "PoPSchema\\CustomPostMediaMutations\\": "layers/Schema/packages/custompostmedia-mutations/tests", + "PoPSchema\\CustomPostMediaWP\\": "layers/Schema/packages/custompostmedia-wp/tests", + "PoPSchema\\CustomPostMedia\\": "layers/Schema/packages/custompostmedia/tests", + "PoPSchema\\CustomPostMetaWP\\": "layers/Schema/packages/custompostmeta-wp/tests", + "PoPSchema\\CustomPostMeta\\": "layers/Schema/packages/custompostmeta/tests", + "PoPSchema\\CustomPostMutationsWP\\": "layers/Schema/packages/custompost-mutations-wp/tests", + "PoPSchema\\CustomPostMutations\\": "layers/Schema/packages/custompost-mutations/tests", + "PoPSchema\\CustomPostsWP\\": "layers/Schema/packages/customposts-wp/tests", + "PoPSchema\\CustomPosts\\": "layers/Schema/packages/customposts/tests", + "PoPSchema\\EventMutationsWPEM\\": "layers/Schema/packages/event-mutations-wp-em/tests", + "PoPSchema\\EventMutations\\": "layers/Schema/packages/event-mutations/tests", + "PoPSchema\\EventsWPEM\\": "layers/Schema/packages/events-wp-em/tests", + "PoPSchema\\Events\\": "layers/Schema/packages/events/tests", + "PoPSchema\\EverythingElseWP\\": "layers/Schema/packages/everythingelse-wp/tests", + "PoPSchema\\EverythingElse\\": "layers/Schema/packages/everythingelse/tests", + "PoPSchema\\GenericCustomPosts\\": "layers/Schema/packages/generic-customposts/tests", + "PoPSchema\\GoogleTranslateDirectiveForCustomPosts\\": "layers/Schema/packages/google-translate-directive-for-customposts/tests", + "PoPSchema\\GoogleTranslateDirective\\": "layers/Schema/packages/google-translate-directive/tests", + "PoPSchema\\HighlightsWP\\": "layers/Schema/packages/highlights-wp/tests", + "PoPSchema\\Highlights\\": "layers/Schema/packages/highlights/tests", + "PoPSchema\\LocationPostsWP\\": "layers/Schema/packages/locationposts-wp/tests", + "PoPSchema\\LocationPosts\\": "layers/Schema/packages/locationposts/tests", + "PoPSchema\\LocationsWPEM\\": "layers/Schema/packages/locations-wp-em/tests", + "PoPSchema\\Locations\\": "layers/Schema/packages/locations/tests", + "PoPSchema\\MediaWP\\": "layers/Schema/packages/media-wp/tests", + "PoPSchema\\Media\\": "layers/Schema/packages/media/tests", + "PoPSchema\\MenusWP\\": "layers/Schema/packages/menus-wp/tests", + "PoPSchema\\Menus\\": "layers/Schema/packages/menus/tests", + "PoPSchema\\MetaQueryWP\\": "layers/Schema/packages/metaquery-wp/tests", + "PoPSchema\\MetaQuery\\": "layers/Schema/packages/metaquery/tests", + "PoPSchema\\Meta\\": "layers/Schema/packages/meta/tests", + "PoPSchema\\NotificationsWP\\": "layers/Schema/packages/notifications-wp/tests", + "PoPSchema\\Notifications\\": "layers/Schema/packages/notifications/tests", + "PoPSchema\\PagesWP\\": "layers/Schema/packages/pages-wp/tests", + "PoPSchema\\Pages\\": "layers/Schema/packages/pages/tests", + "PoPSchema\\PostMutations\\": "layers/Schema/packages/post-mutations/tests", + "PoPSchema\\PostTagsWP\\": "layers/Schema/packages/post-tags-wp/tests", + "PoPSchema\\PostTags\\": "layers/Schema/packages/post-tags/tests", + "PoPSchema\\PostsWP\\": "layers/Schema/packages/posts-wp/tests", + "PoPSchema\\Posts\\": "layers/Schema/packages/posts/tests", + "PoPSchema\\QueriedObjectWP\\": "layers/Schema/packages/queriedobject-wp/tests", + "PoPSchema\\QueriedObject\\": "layers/Schema/packages/queriedobject/tests", + "PoPSchema\\SchemaCommons\\": "layers/Schema/packages/schema-commons/tests", + "PoPSchema\\StancesWP\\": "layers/Schema/packages/stances-wp/tests", + "PoPSchema\\Stances\\": "layers/Schema/packages/stances/tests", + "PoPSchema\\TagsWP\\": "layers/Schema/packages/tags-wp/tests", + "PoPSchema\\Tags\\": "layers/Schema/packages/tags/tests", + "PoPSchema\\TaxonomiesWP\\": "layers/Schema/packages/taxonomies-wp/tests", + "PoPSchema\\Taxonomies\\": "layers/Schema/packages/taxonomies/tests", + "PoPSchema\\TaxonomyMetaWP\\": "layers/Schema/packages/taxonomymeta-wp/tests", + "PoPSchema\\TaxonomyMeta\\": "layers/Schema/packages/taxonomymeta/tests", + "PoPSchema\\TaxonomyQueryWP\\": "layers/Schema/packages/taxonomyquery-wp/tests", + "PoPSchema\\TaxonomyQuery\\": "layers/Schema/packages/taxonomyquery/tests", + "PoPSchema\\TranslateDirectiveACL\\": "layers/Schema/packages/translate-directive-acl/tests", + "PoPSchema\\TranslateDirective\\": "layers/Schema/packages/translate-directive/tests", + "PoPSchema\\UserMetaWP\\": "layers/Schema/packages/usermeta-wp/tests", + "PoPSchema\\UserMeta\\": "layers/Schema/packages/usermeta/tests", + "PoPSchema\\UserRolesACL\\": "layers/Schema/packages/user-roles-acl/tests", + "PoPSchema\\UserRolesAccessControl\\": "layers/Schema/packages/user-roles-access-control/tests", + "PoPSchema\\UserRolesWP\\": "layers/Schema/packages/user-roles-wp/tests", + "PoPSchema\\UserRoles\\": "layers/Schema/packages/user-roles/tests", + "PoPSchema\\UserStateAccessControl\\": "layers/Schema/packages/user-state-access-control/tests", + "PoPSchema\\UserStateMutationsWP\\": "layers/Schema/packages/user-state-mutations-wp/tests", + "PoPSchema\\UserStateMutations\\": "layers/Schema/packages/user-state-mutations/tests", + "PoPSchema\\UserStateWP\\": "layers/Schema/packages/user-state-wp/tests", + "PoPSchema\\UserState\\": "layers/Schema/packages/user-state/tests", + "PoPSchema\\UsersWP\\": "layers/Schema/packages/users-wp/tests", + "PoPSchema\\Users\\": "layers/Schema/packages/users/tests", + "PoPSitesWassup\\CommentMutations\\": "layers/Wassup/packages/comment-mutations/tests", + "PoPSitesWassup\\ContactUsMutations\\": "layers/Wassup/packages/contactus-mutations/tests", + "PoPSitesWassup\\ContactUserMutations\\": "layers/Wassup/packages/contactuser-mutations/tests", + "PoPSitesWassup\\CustomPostLinkMutations\\": "layers/Wassup/packages/custompostlink-mutations/tests", + "PoPSitesWassup\\CustomPostMutations\\": "layers/Wassup/packages/custompost-mutations/tests", + "PoPSitesWassup\\EventLinkMutations\\": "layers/Wassup/packages/eventlink-mutations/tests", + "PoPSitesWassup\\EventMutations\\": "layers/Wassup/packages/event-mutations/tests", + "PoPSitesWassup\\EverythingElseMutations\\": "layers/Wassup/packages/everythingelse-mutations/tests", + "PoPSitesWassup\\FlagMutations\\": "layers/Wassup/packages/flag-mutations/tests", + "PoPSitesWassup\\FormMutations\\": "layers/Wassup/packages/form-mutations/tests", + "PoPSitesWassup\\GravityFormsMutations\\": "layers/Wassup/packages/gravityforms-mutations/tests", + "PoPSitesWassup\\HighlightMutations\\": "layers/Wassup/packages/highlight-mutations/tests", + "PoPSitesWassup\\LocationMutations\\": "layers/Wassup/packages/location-mutations/tests", + "PoPSitesWassup\\LocationPostLinkMutations\\": "layers/Wassup/packages/locationpostlink-mutations/tests", + "PoPSitesWassup\\LocationPostMutations\\": "layers/Wassup/packages/locationpost-mutations/tests", + "PoPSitesWassup\\NewsletterMutations\\": "layers/Wassup/packages/newsletter-mutations/tests", + "PoPSitesWassup\\NotificationMutations\\": "layers/Wassup/packages/notification-mutations/tests", + "PoPSitesWassup\\PostLinkMutations\\": "layers/Wassup/packages/postlink-mutations/tests", + "PoPSitesWassup\\PostMutations\\": "layers/Wassup/packages/post-mutations/tests", + "PoPSitesWassup\\ShareMutations\\": "layers/Wassup/packages/share-mutations/tests", + "PoPSitesWassup\\SocialNetworkMutations\\": "layers/Wassup/packages/socialnetwork-mutations/tests", + "PoPSitesWassup\\StanceMutations\\": "layers/Wassup/packages/stance-mutations/tests", + "PoPSitesWassup\\SystemMutations\\": "layers/Wassup/packages/system-mutations/tests", + "PoPSitesWassup\\UserStateMutations\\": "layers/Wassup/packages/user-state-mutations/tests", + "PoPSitesWassup\\VolunteerMutations\\": "layers/Wassup/packages/volunteer-mutations/tests", + "PoPSitesWassup\\Wassup\\": "layers/Wassup/packages/wassup/tests", + "PoP\\APIClients\\": "layers/API/packages/api-clients/tests", + "PoP\\APIEndpointsForWP\\": "layers/API/packages/api-endpoints-for-wp/tests", + "PoP\\APIEndpoints\\": "layers/API/packages/api-endpoints/tests", + "PoP\\APIMirrorQuery\\": "layers/API/packages/api-mirrorquery/tests", + "PoP\\API\\": "layers/API/packages/api/tests", + "PoP\\AccessControl\\": "layers/Engine/packages/access-control/tests", + "PoP\\ApplicationWP\\": "layers/SiteBuilder/packages/application-wp/tests", + "PoP\\Application\\": "layers/SiteBuilder/packages/application/tests", + "PoP\\Base36Definitions\\": "layers/SiteBuilder/packages/definitions-base36/tests", + "PoP\\CacheControl\\": "layers/Engine/packages/cache-control/tests", + "PoP\\ComponentModel\\": "layers/Engine/packages/component-model/tests", + "PoP\\ConfigurableSchemaFeedback\\": "layers/Engine/packages/configurable-schema-feedback/tests", + "PoP\\ConfigurationComponentModel\\": "layers/SiteBuilder/packages/component-model-configuration/tests", + "PoP\\DefinitionPersistence\\": "layers/SiteBuilder/packages/definitionpersistence/tests", + "PoP\\Definitions\\": "layers/Engine/packages/definitions/tests", + "PoP\\EmojiDefinitions\\": "layers/SiteBuilder/packages/definitions-emoji/tests", + "PoP\\EngineWP\\": "layers/Engine/packages/engine-wp/tests", + "PoP\\Engine\\": "layers/Engine/packages/engine/tests", + "PoP\\FieldQuery\\": "layers/Engine/packages/field-query/tests", + "PoP\\FileStore\\": "layers/Engine/packages/filestore/tests", + "PoP\\FunctionFields\\": "layers/Engine/packages/function-fields/tests", + "PoP\\GraphQLAPI\\": "layers/API/packages/api-graphql/tests", + "PoP\\GuzzleHelpers\\": "layers/Engine/packages/guzzle-helpers/tests", + "PoP\\HooksWP\\": "layers/Engine/packages/hooks-wp/tests", + "PoP\\Hooks\\": "layers/Engine/packages/hooks/tests", + "PoP\\LooseContracts\\": "layers/Engine/packages/loosecontracts/tests", + "PoP\\MandatoryDirectivesByConfiguration\\": "layers/Engine/packages/mandatory-directives-by-configuration/tests", + "PoP\\ModuleRouting\\": "layers/Engine/packages/modulerouting/tests", + "PoP\\Multisite\\": "layers/SiteBuilder/packages/multisite/tests", + "PoP\\QueryParsing\\": "layers/Engine/packages/query-parsing/tests", + "PoP\\RESTAPI\\": "layers/API/packages/api-rest/tests", + "PoP\\ResourceLoader\\": "layers/SiteBuilder/packages/resourceloader/tests", + "PoP\\Resources\\": "layers/SiteBuilder/packages/resources/tests", + "PoP\\Root\\": "layers/Engine/packages/root/tests", + "PoP\\RoutingWP\\": "layers/Engine/packages/routing-wp/tests", + "PoP\\Routing\\": "layers/Engine/packages/routing/tests", + "PoP\\SPA\\": "layers/SiteBuilder/packages/spa/tests", + "PoP\\SSG\\": "layers/SiteBuilder/packages/static-site-generator/tests", + "PoP\\SiteWP\\": "layers/SiteBuilder/packages/site-wp/tests", + "PoP\\Site\\": "layers/SiteBuilder/packages/site/tests", + "PoP\\TraceTools\\": "layers/Engine/packages/trace-tools/tests", + "PoP\\TranslationWP\\": "layers/Engine/packages/translation-wp/tests", + "PoP\\Translation\\": "layers/Engine/packages/translation/tests" + } + }, + "extra": { + "wordpress-install-dir": "vendor/wordpress/wordpress", + "merge-plugin": { + "include": [ + "composer.local.json" + ], + "recurse": true, + "replace": false, + "ignore-duplicates": false, + "merge-dev": true, + "merge-extra": false, + "merge-extra-deep": false, + "merge-scripts": false + } + }, + "replace": { + "getpop/access-control": "self.version", + "getpop/api": "self.version", + "getpop/api-clients": "self.version", + "getpop/api-endpoints": "self.version", + "getpop/api-endpoints-for-wp": "self.version", + "getpop/api-graphql": "self.version", + "getpop/api-mirrorquery": "self.version", + "getpop/api-rest": "self.version", + "getpop/application": "self.version", + "getpop/application-wp": "self.version", + "getpop/cache-control": "self.version", + "getpop/component-model": "self.version", + "getpop/component-model-configuration": "self.version", + "getpop/configurable-schema-feedback": "self.version", + "getpop/definitionpersistence": "self.version", + "getpop/definitions": "self.version", + "getpop/definitions-base36": "self.version", + "getpop/definitions-emoji": "self.version", + "getpop/engine": "self.version", + "getpop/engine-wp": "self.version", + "getpop/engine-wp-bootloader": "self.version", + "getpop/field-query": "self.version", + "getpop/filestore": "self.version", + "getpop/function-fields": "self.version", + "getpop/guzzle-helpers": "self.version", + "getpop/hooks": "self.version", + "getpop/hooks-wp": "self.version", + "getpop/loosecontracts": "self.version", + "getpop/mandatory-directives-by-configuration": "self.version", + "getpop/migrate-api": "self.version", + "getpop/migrate-api-graphql": "self.version", + "getpop/migrate-component-model": "self.version", + "getpop/migrate-component-model-configuration": "self.version", + "getpop/migrate-engine": "self.version", + "getpop/migrate-engine-wp": "self.version", + "getpop/migrate-static-site-generator": "self.version", + "getpop/modulerouting": "self.version", + "getpop/multisite": "self.version", + "getpop/query-parsing": "self.version", + "getpop/resourceloader": "self.version", + "getpop/resources": "self.version", + "getpop/root": "self.version", + "getpop/routing": "self.version", + "getpop/routing-wp": "self.version", + "getpop/site": "self.version", + "getpop/site-wp": "self.version", + "getpop/spa": "self.version", + "getpop/static-site-generator": "self.version", + "getpop/trace-tools": "self.version", + "getpop/translation": "self.version", + "getpop/translation-wp": "self.version", + "graphql-api/convert-case-directives": "self.version", + "graphql-api/graphql-api-for-wp": "self.version", + "graphql-api/schema-feedback": "self.version", + "graphql-by-pop/graphql-clients-for-wp": "self.version", + "graphql-by-pop/graphql-endpoint-for-wp": "self.version", + "graphql-by-pop/graphql-parser": "self.version", + "graphql-by-pop/graphql-query": "self.version", + "graphql-by-pop/graphql-request": "self.version", + "graphql-by-pop/graphql-server": "self.version", + "leoloso/examples-for-pop": "self.version", + "pop-migrate-everythingelse/cssconverter": "self.version", + "pop-migrate-everythingelse/ssr": "self.version", + "pop-schema/basic-directives": "self.version", + "pop-schema/block-metadata-for-wp": "self.version", + "pop-schema/categories": "self.version", + "pop-schema/categories-wp": "self.version", + "pop-schema/cdn-directive": "self.version", + "pop-schema/comment-mutations": "self.version", + "pop-schema/comment-mutations-wp": "self.version", + "pop-schema/commentmeta": "self.version", + "pop-schema/commentmeta-wp": "self.version", + "pop-schema/comments": "self.version", + "pop-schema/comments-wp": "self.version", + "pop-schema/convert-case-directives": "self.version", + "pop-schema/custompost-mutations": "self.version", + "pop-schema/custompost-mutations-wp": "self.version", + "pop-schema/custompostmedia": "self.version", + "pop-schema/custompostmedia-mutations": "self.version", + "pop-schema/custompostmedia-mutations-wp": "self.version", + "pop-schema/custompostmedia-wp": "self.version", + "pop-schema/custompostmeta": "self.version", + "pop-schema/custompostmeta-wp": "self.version", + "pop-schema/customposts": "self.version", + "pop-schema/customposts-wp": "self.version", + "pop-schema/event-mutations": "self.version", + "pop-schema/event-mutations-wp-em": "self.version", + "pop-schema/events": "self.version", + "pop-schema/events-wp-em": "self.version", + "pop-schema/everythingelse": "self.version", + "pop-schema/everythingelse-wp": "self.version", + "pop-schema/generic-customposts": "self.version", + "pop-schema/google-translate-directive": "self.version", + "pop-schema/google-translate-directive-for-customposts": "self.version", + "pop-schema/highlights": "self.version", + "pop-schema/highlights-wp": "self.version", + "pop-schema/locationposts": "self.version", + "pop-schema/locationposts-wp": "self.version", + "pop-schema/locations": "self.version", + "pop-schema/locations-wp-em": "self.version", + "pop-schema/media": "self.version", + "pop-schema/media-wp": "self.version", + "pop-schema/menus": "self.version", + "pop-schema/menus-wp": "self.version", + "pop-schema/meta": "self.version", + "pop-schema/metaquery": "self.version", + "pop-schema/metaquery-wp": "self.version", + "pop-schema/migrate-categories": "self.version", + "pop-schema/migrate-categories-wp": "self.version", + "pop-schema/migrate-commentmeta": "self.version", + "pop-schema/migrate-commentmeta-wp": "self.version", + "pop-schema/migrate-comments": "self.version", + "pop-schema/migrate-comments-wp": "self.version", + "pop-schema/migrate-custompostmedia": "self.version", + "pop-schema/migrate-custompostmedia-wp": "self.version", + "pop-schema/migrate-custompostmeta": "self.version", + "pop-schema/migrate-custompostmeta-wp": "self.version", + "pop-schema/migrate-customposts": "self.version", + "pop-schema/migrate-customposts-wp": "self.version", + "pop-schema/migrate-events": "self.version", + "pop-schema/migrate-events-wp-em": "self.version", + "pop-schema/migrate-everythingelse": "self.version", + "pop-schema/migrate-locations": "self.version", + "pop-schema/migrate-locations-wp-em": "self.version", + "pop-schema/migrate-media": "self.version", + "pop-schema/migrate-media-wp": "self.version", + "pop-schema/migrate-meta": "self.version", + "pop-schema/migrate-metaquery": "self.version", + "pop-schema/migrate-metaquery-wp": "self.version", + "pop-schema/migrate-pages": "self.version", + "pop-schema/migrate-pages-wp": "self.version", + "pop-schema/migrate-post-tags": "self.version", + "pop-schema/migrate-post-tags-wp": "self.version", + "pop-schema/migrate-posts": "self.version", + "pop-schema/migrate-posts-wp": "self.version", + "pop-schema/migrate-queriedobject": "self.version", + "pop-schema/migrate-queriedobject-wp": "self.version", + "pop-schema/migrate-tags": "self.version", + "pop-schema/migrate-tags-wp": "self.version", + "pop-schema/migrate-taxonomies": "self.version", + "pop-schema/migrate-taxonomies-wp": "self.version", + "pop-schema/migrate-taxonomymeta": "self.version", + "pop-schema/migrate-taxonomymeta-wp": "self.version", + "pop-schema/migrate-taxonomyquery": "self.version", + "pop-schema/migrate-taxonomyquery-wp": "self.version", + "pop-schema/migrate-usermeta": "self.version", + "pop-schema/migrate-usermeta-wp": "self.version", + "pop-schema/migrate-users": "self.version", + "pop-schema/migrate-users-wp": "self.version", + "pop-schema/notifications": "self.version", + "pop-schema/notifications-wp": "self.version", + "pop-schema/pages": "self.version", + "pop-schema/pages-wp": "self.version", + "pop-schema/post-mutations": "self.version", + "pop-schema/post-tags": "self.version", + "pop-schema/post-tags-wp": "self.version", + "pop-schema/posts": "self.version", + "pop-schema/posts-wp": "self.version", + "pop-schema/queriedobject": "self.version", + "pop-schema/queriedobject-wp": "self.version", + "pop-schema/schema-commons": "self.version", + "pop-schema/stances": "self.version", + "pop-schema/stances-wp": "self.version", + "pop-schema/tags": "self.version", + "pop-schema/tags-wp": "self.version", + "pop-schema/taxonomies": "self.version", + "pop-schema/taxonomies-wp": "self.version", + "pop-schema/taxonomymeta": "self.version", + "pop-schema/taxonomymeta-wp": "self.version", + "pop-schema/taxonomyquery": "self.version", + "pop-schema/taxonomyquery-wp": "self.version", + "pop-schema/translate-directive": "self.version", + "pop-schema/translate-directive-acl": "self.version", + "pop-schema/user-roles": "self.version", + "pop-schema/user-roles-access-control": "self.version", + "pop-schema/user-roles-acl": "self.version", + "pop-schema/user-roles-wp": "self.version", + "pop-schema/user-state": "self.version", + "pop-schema/user-state-access-control": "self.version", + "pop-schema/user-state-mutations": "self.version", + "pop-schema/user-state-mutations-wp": "self.version", + "pop-schema/user-state-wp": "self.version", + "pop-schema/usermeta": "self.version", + "pop-schema/usermeta-wp": "self.version", + "pop-schema/users": "self.version", + "pop-schema/users-wp": "self.version", + "pop-sites-wassup/comment-mutations": "self.version", + "pop-sites-wassup/contactus-mutations": "self.version", + "pop-sites-wassup/contactuser-mutations": "self.version", + "pop-sites-wassup/custompost-mutations": "self.version", + "pop-sites-wassup/custompostlink-mutations": "self.version", + "pop-sites-wassup/event-mutations": "self.version", + "pop-sites-wassup/eventlink-mutations": "self.version", + "pop-sites-wassup/everythingelse-mutations": "self.version", + "pop-sites-wassup/flag-mutations": "self.version", + "pop-sites-wassup/form-mutations": "self.version", + "pop-sites-wassup/gravityforms-mutations": "self.version", + "pop-sites-wassup/highlight-mutations": "self.version", + "pop-sites-wassup/location-mutations": "self.version", + "pop-sites-wassup/locationpost-mutations": "self.version", + "pop-sites-wassup/locationpostlink-mutations": "self.version", + "pop-sites-wassup/newsletter-mutations": "self.version", + "pop-sites-wassup/notification-mutations": "self.version", + "pop-sites-wassup/post-mutations": "self.version", + "pop-sites-wassup/postlink-mutations": "self.version", + "pop-sites-wassup/share-mutations": "self.version", + "pop-sites-wassup/socialnetwork-mutations": "self.version", + "pop-sites-wassup/stance-mutations": "self.version", + "pop-sites-wassup/system-mutations": "self.version", + "pop-sites-wassup/user-state-mutations": "self.version", + "pop-sites-wassup/volunteer-mutations": "self.version", + "pop-sites-wassup/wassup": "self.version" + }, + "authors": [ + { + "name": "Leonardo Losoviz", + "email": "leo@getpop.org", + "homepage": "https://getpop.org" + } + ], + "description": "Monorepo for all the PoP packages", + "license": "GPL-2.0-or-later", + "config": { + "sort-packages": true, + "platform-check": false + }, + "repositories": [ + { + "type": "composer", + "url": "https://wpackagist.org" + }, + { + "type": "vcs", + "url": "https://github.com/leoloso/wp-muplugin-loader.git" + }, + { + "type": "vcs", + "url": "https://github.com/mcaskill/composer-merge-plugin.git" + } + ], + "scripts": { + "test": "phpunit", + "check-style": "phpcs -n src $(monorepo-builder source-packages --subfolder=src --subfolder=tests)", + "fix-style": "phpcbf -n src $(monorepo-builder source-packages --subfolder=src --subfolder=tests)", + "analyse": "ci/phpstan.sh \". $(monorepo-builder source-packages --skip-unmigrated)\"", + "preview-src-downgrade": "rector process $(monorepo-builder source-packages --subfolder=src) --config=rector-downgrade-code.php --ansi --dry-run || true", + "preview-vendor-downgrade": "layers/Engine/packages/root/ci/downgrade_code.sh 7.1 rector-downgrade-code.php --dry-run || true", + "preview-code-downgrade": [ + "@preview-src-downgrade", + "@preview-vendor-downgrade" + ], + "build-server": [ + "lando init --source remote --remote-url https://wordpress.org/latest.tar.gz --recipe wordpress --webroot wordpress --name graphql-api-dev", + "@start-server" + ], + "start-server": [ + "cd layers/GraphQLAPIForWP/plugins/graphql-api-for-wp && composer install", + "lando start" + ], + "rebuild-server": "lando rebuild -y", + "merge-monorepo": "monorepo-builder merge --ansi", + "propagate-monorepo": "monorepo-builder propagate --ansi", + "validate-monorepo": "monorepo-builder validate --ansi", + "release": "monorepo-builder release patch --ansi" + }, + "minimum-stability": "dev", + "prefer-stable": true +} +"#, + manipulator.get_contents() + ); } diff --git a/crates/shirabe/tests/package/archiver/archivable_files_finder_test.rs b/crates/shirabe/tests/package/archiver/archivable_files_finder_test.rs index 5454074..48f0fc5 100644 --- a/crates/shirabe/tests/package/archiver/archivable_files_finder_test.rs +++ b/crates/shirabe/tests/package/archiver/archivable_files_finder_test.rs @@ -26,20 +26,20 @@ impl Drop for TearDown { // These set up a temp directory tree (including a git repo) and assert the files the finder // selects with manual/git/skip excludes; the git-backed fixture setup is not ported. +#[ignore = "setUp needs TestCase::getUniqueTmpDirectory to build the on-disk fixture tree; not ported"] #[test] -#[ignore = "needs a temp directory tree and git-backed fixtures to drive ArchivableFilesFinder; not ported"] fn test_manual_excludes() { todo!() } +#[ignore = "setUp needs TestCase::getUniqueTmpDirectory plus skipIfNotExecutable/Process::fromShellCommandline/PharData/RecursiveIteratorIterator; none ported"] #[test] -#[ignore = "needs a temp directory tree and git-backed fixtures to drive ArchivableFilesFinder; not ported"] fn test_git_excludes() { todo!() } +#[ignore = "setUp needs TestCase::getUniqueTmpDirectory to build the on-disk fixture tree; not ported"] #[test] -#[ignore = "needs a temp directory tree and git-backed fixtures to drive ArchivableFilesFinder; not ported"] fn test_skip_excludes() { todo!() } diff --git a/crates/shirabe/tests/package/archiver/archive_manager_test.rs b/crates/shirabe/tests/package/archiver/archive_manager_test.rs index b3b61f8..2712da6 100644 --- a/crates/shirabe/tests/package/archiver/archive_manager_test.rs +++ b/crates/shirabe/tests/package/archiver/archive_manager_test.rs @@ -29,31 +29,31 @@ impl Drop for TearDown { // the filename-derivation helpers over packages; the archiving and fixture setup are not // ported. #[test] -#[ignore = "ArchiveManager builds archives via PharData (todo!()) over fixtures; not ported"] +#[ignore = "setupPackage requires CompletePackage::setSourceType, not exposed on the package handle API; also needs Factory-built ArchiveManager (set_up) which is unported"] fn test_unknown_format() { todo!() } #[test] -#[ignore = "ArchiveManager builds archives via PharData (todo!()) over fixtures; not ported"] +#[ignore = "needs setupGitRepo + PharData tar archiving and Factory-built ArchiveManager (set_up), and setupPackage's setSourceType which is not exposed on the package handle API"] fn test_archive_tar() { todo!() } #[test] -#[ignore = "ArchiveManager builds archives via PharData (todo!()) over fixtures; not ported"] +#[ignore = "needs setupGitRepo + PharData tar archiving and Factory-built ArchiveManager (set_up), and setupPackage's setSourceType which is not exposed on the package handle API"] fn test_archive_custom_file_name() { todo!() } #[test] -#[ignore = "ArchiveManager builds archives via PharData (todo!()) over fixtures; not ported"] +#[ignore = "setupPackage requires CompletePackage::setSourceType, not exposed on the package handle API; also needs Factory-built ArchiveManager (set_up) which is unported"] fn test_get_package_filename_parts() { todo!() } #[test] -#[ignore = "ArchiveManager builds archives via PharData (todo!()) over fixtures; not ported"] +#[ignore = "setupPackage requires CompletePackage::setSourceType, not exposed on the package handle API; also needs Factory-built ArchiveManager (set_up) which is unported"] fn test_get_package_filename() { todo!() } diff --git a/crates/shirabe/tests/package/archiver/phar_archiver_test.rs b/crates/shirabe/tests/package/archiver/phar_archiver_test.rs index 414aa36..385177b 100644 --- a/crates/shirabe/tests/package/archiver/phar_archiver_test.rs +++ b/crates/shirabe/tests/package/archiver/phar_archiver_test.rs @@ -1,13 +1,13 @@ //! ref: composer/tests/Composer/Test/Package/Archiver/PharArchiverTest.php +#[ignore = "setUp/setupDummyRepo need TestCase::getUniqueTmpDirectory plus Filesystem to build the on-disk fixture tree; not ported"] #[test] -#[ignore = "PharArchiver::archive builds a tar via PharData, which is todo!() in the php-shim"] fn test_tar_archive() { todo!() } +#[ignore = "setUp/setupDummyRepo need TestCase::getUniqueTmpDirectory plus Filesystem to build the on-disk fixture tree; not ported"] #[test] -#[ignore = "PharArchiver::archive builds a zip via ZipArchive, which is todo!() in the php-shim"] fn test_zip_archive() { todo!() } diff --git a/crates/shirabe/tests/package/archiver/zip_archiver_test.rs b/crates/shirabe/tests/package/archiver/zip_archiver_test.rs index 5f41c78..5b51ce9 100644 --- a/crates/shirabe/tests/package/archiver/zip_archiver_test.rs +++ b/crates/shirabe/tests/package/archiver/zip_archiver_test.rs @@ -28,19 +28,19 @@ impl Drop for TearDown { // ZipArchiver::archive builds a zip via ZipArchive, which is todo!() in the php-shim. #[test] -#[ignore = "ZipArchiver::archive builds a zip via ZipArchive, which is todo!() in the php-shim"] +#[ignore = "ArchiverTestCase infrastructure (setup_package/setup_dummy_repo/test_dir) not ported; ZipArchive shim lacks get_from_name"] fn test_simple_files() { todo!() } #[test] -#[ignore = "ZipArchiver::archive builds a zip via ZipArchive, which is todo!() in the php-shim"] +#[ignore = "ArchiverTestCase infrastructure (setup_package/setup_dummy_repo/test_dir) not ported; ZipArchive shim lacks get_from_name"] fn test_gitignore_exclude_negation() { todo!() } #[test] -#[ignore = "ZipArchiver::archive builds a zip via ZipArchive, which is todo!() in the php-shim"] +#[ignore = "ArchiverTestCase infrastructure (setup_package/setup_dummy_repo/test_dir) not ported; ZipArchive shim lacks get_from_name"] fn test_folder_with_backslashes() { todo!() } diff --git a/crates/shirabe/tests/package/base_package_test.rs b/crates/shirabe/tests/package/base_package_test.rs index 0d77f49..1056de1 100644 --- a/crates/shirabe/tests/package/base_package_test.rs +++ b/crates/shirabe/tests/package/base_package_test.rs @@ -35,8 +35,8 @@ fn test_set_another_repository() { // on an abstract BasePackage to drive getFullPrettyVersion(). Reproducing those exact // getter values requires mocking; a real package cannot carry the pretty version // "PrettyVersion" together with isDev() == true. +#[ignore = "requires mocking isDev/getSourceType/getPrettyVersion/getSourceReference on an abstract BasePackage; no mock infrastructure exists and a real package cannot carry prettyVersion \"PrettyVersion\" with isDev() == true"] #[test] -#[ignore = "mocks isDev/getSourceType/getPrettyVersion/getSourceReference on an abstract BasePackage; not reproducible with a real package"] fn test_format_version_for_dev_package() { todo!() } diff --git a/crates/shirabe/tests/package/dumper/array_dumper_test.rs b/crates/shirabe/tests/package/dumper/array_dumper_test.rs index 6ed12fe..64880f1 100644 --- a/crates/shirabe/tests/package/dumper/array_dumper_test.rs +++ b/crates/shirabe/tests/package/dumper/array_dumper_test.rs @@ -83,7 +83,7 @@ fn test_dump_abandoned_replacement() { // arrays, a DateTime and Link maps, then checks the corresponding dumped key. Reproducing // that dynamic dispatch faithfully would require per-case wiring for every property type. #[test] -#[ignore = "exercises ~25 dynamic set<Property> calls over heterogeneous value types; not reproduced here"] +#[ignore = "RootPackageHandle lacks set_type/set_release_date/set_binaries/set_php_ext setters required by the data provider's type/time/bin/php-ext cases"] fn test_keys() { todo!() } diff --git a/crates/shirabe/tests/package/loader/array_loader_test.rs b/crates/shirabe/tests/package/loader/array_loader_test.rs index 2dc4f3e..d35f671 100644 --- a/crates/shirabe/tests/package/loader/array_loader_test.rs +++ b/crates/shirabe/tests/package/loader/array_loader_test.rs @@ -1,170 +1,647 @@ //! ref: composer/tests/Composer/Test/Package/Loader/ArrayLoaderTest.php -use shirabe::package::loader::ArrayLoader; +use indexmap::IndexMap; +use shirabe::package::ArrayDumper; +use shirabe::package::Link; +use shirabe::package::loader::{ArrayLoader, LoaderInterface}; +use shirabe::package::version::VersionParser; +use shirabe_php_shim::PhpMixed; fn set_up() -> ArrayLoader { ArrayLoader::new(None, false) } -// ArrayLoader::load parses version/link constraints through a look-around regex the regex -// crate cannot compile. +fn s(value: &str) -> PhpMixed { + PhpMixed::String(value.to_string()) +} + +fn map(entries: Vec<(&str, PhpMixed)>) -> PhpMixed { + let mut m: IndexMap<String, PhpMixed> = IndexMap::new(); + for (k, v) in entries { + m.insert(k.to_string(), v); + } + PhpMixed::Array(m) +} + +fn make_config(entries: Vec<(&str, PhpMixed)>) -> IndexMap<String, PhpMixed> { + let mut m: IndexMap<String, PhpMixed> = IndexMap::new(); + for (k, v) in entries { + m.insert(k.to_string(), v); + } + m +} + +fn list(items: Vec<PhpMixed>) -> PhpMixed { + PhpMixed::List(items) +} + +/// ref: ArrayLoaderTest::parseDumpProvider valid config +fn valid_config() -> IndexMap<String, PhpMixed> { + make_config(vec![ + ("name", s("A/B")), + ("version", s("1.2.3")), + ("version_normalized", s("1.2.3.0")), + ("description", s("Foo bar")), + ("type", s("library")), + ("keywords", list(vec![s("a"), s("b"), s("c")])), + ("homepage", s("http://example.com")), + ("license", list(vec![s("MIT"), s("GPLv3")])), + ( + "authors", + list(vec![map(vec![ + ("name", s("Bob")), + ("email", s("bob@example.org")), + ("homepage", s("example.org")), + ("role", s("Developer")), + ])]), + ), + ( + "funding", + list(vec![map(vec![ + ("type", s("example")), + ("url", s("https://example.org/fund")), + ])]), + ), + ("require", map(vec![("foo/bar", s("1.0"))])), + ("require-dev", map(vec![("foo/baz", s("1.0"))])), + ("replace", map(vec![("foo/qux", s("1.0"))])), + ("conflict", map(vec![("foo/quux", s("1.0"))])), + ("provide", map(vec![("foo/quuux", s("1.0"))])), + ( + "autoload", + map(vec![ + ("psr-0", map(vec![("Ns\\Prefix", s("path"))])), + ("classmap", list(vec![s("path"), s("path2")])), + ]), + ), + ("include-path", list(vec![s("path3"), s("path4")])), + ("target-dir", s("some/prefix")), + ( + "extra", + map(vec![( + "random", + map(vec![("things", s("of")), ("any", s("shape"))]), + )]), + ), + ("bin", list(vec![s("bin1"), s("bin/foo")])), + ( + "archive", + map(vec![( + "exclude", + list(vec![s("/foo/bar"), s("baz"), s("!/foo/bar/baz")]), + )]), + ), + ( + "transport-options", + map(vec![( + "ssl", + map(vec![("local_cert", s("/opt/certs/test.pem"))]), + )]), + ), + ("abandoned", s("foo/bar")), + ]) +} + +/// ref: ArrayLoaderTest::fixConfigWhenLoadConfigIsFalse +fn fix_config_when_load_config_is_false( + config: &IndexMap<String, PhpMixed>, +) -> IndexMap<String, PhpMixed> { + let mut expected_config = config.clone(); + expected_config.shift_remove("transport-options"); + expected_config +} + +#[ignore] #[test] -#[ignore = "ArrayLoader::load parses constraints via a look-around regex the regex crate cannot compile"] fn test_self_version() { - let _loader = set_up(); - todo!() + let loader = set_up(); + let config = make_config(vec![ + ("name", s("A")), + ("version", s("1.2.3.4")), + ("replace", map(vec![("foo", s("self.version"))])), + ]); + + let package = loader.load(config, None).unwrap(); + let replaces = package.get_replaces(); + assert_eq!( + "== 1.2.3.4", + replaces.get("foo").unwrap().get_constraint().to_string() + ); } +#[ignore] #[test] -#[ignore = "ArrayLoader::load parses constraints via a look-around regex the regex crate cannot compile"] fn test_type_default() { - let _loader = set_up(); - todo!() + let loader = set_up(); + let config = make_config(vec![("name", s("A")), ("version", s("1.0"))]); + + let package = loader.load(config, None).unwrap(); + assert_eq!("library", package.get_type()); + + let config = make_config(vec![ + ("name", s("A")), + ("version", s("1.0")), + ("type", s("foo")), + ]); + + let package = loader.load(config, None).unwrap(); + assert_eq!("foo", package.get_type()); } +#[ignore] #[test] -#[ignore = "ArrayLoader::load parses constraints via a look-around regex the regex crate cannot compile"] fn test_normalized_version_optimization() { - let _loader = set_up(); - todo!() + let loader = set_up(); + let config = make_config(vec![("name", s("A")), ("version", s("1.2.3"))]); + + let package = loader.load(config, None).unwrap(); + assert_eq!("1.2.3.0", package.get_version()); + + let config = make_config(vec![ + ("name", s("A")), + ("version", s("1.2.3")), + ("version_normalized", s("1.2.3.4")), + ]); + + let package = loader.load(config, None).unwrap(); + assert_eq!("1.2.3.4", package.get_version()); } +#[ignore] #[test] -#[ignore = "ArrayLoader::load parses constraints via a look-around regex the regex crate cannot compile"] fn test_parse_dump_default_load_config() { - let _loader = set_up(); - todo!() + let loader = set_up(); + let config = valid_config(); + let package = loader.load(config.clone(), None).unwrap(); + let dumper = ArrayDumper::new(); + let expected_config = fix_config_when_load_config_is_false(&config); + assert_eq!(expected_config, dumper.dump(package)); } +#[ignore] #[test] -#[ignore = "ArrayLoader::load parses constraints via a look-around regex the regex crate cannot compile"] fn test_parse_dump_true_load_config() { - let _loader = set_up(); - todo!() + let config = valid_config(); + let loader = ArrayLoader::new(None, true); + let package = loader.load(config.clone(), None).unwrap(); + let dumper = ArrayDumper::new(); + let expected_config = config; + assert_eq!(expected_config, dumper.dump(package)); } +#[ignore] #[test] -#[ignore = "ArrayLoader::load parses constraints via a look-around regex the regex crate cannot compile"] fn test_parse_dump_false_load_config() { - let _loader = set_up(); - todo!() + let config = valid_config(); + let loader = ArrayLoader::new(None, false); + let package = loader.load(config.clone(), None).unwrap(); + let dumper = ArrayDumper::new(); + let expected_config = fix_config_when_load_config_is_false(&config); + assert_eq!(expected_config, dumper.dump(package)); } +#[ignore] #[test] -#[ignore = "ArrayLoader::load parses constraints via a look-around regex the regex crate cannot compile"] fn test_package_with_branch_alias() { - let _loader = set_up(); - todo!() + let loader = set_up(); + let config = make_config(vec![ + ("name", s("A")), + ("version", s("dev-master")), + ( + "extra", + map(vec![( + "branch-alias", + map(vec![("dev-master", s("1.0.x-dev"))]), + )]), + ), + ]); + + let package = loader.load(config, None).unwrap(); + + assert!(package.as_complete_alias_package().is_some()); + assert_eq!("1.0.x-dev", package.get_pretty_version()); + + let config = make_config(vec![ + ("name", s("A")), + ("version", s("dev-master")), + ( + "extra", + map(vec![( + "branch-alias", + map(vec![("dev-master", s("1.0-dev"))]), + )]), + ), + ]); + + let package = loader.load(config, None).unwrap(); + + assert!(package.as_complete_alias_package().is_some()); + assert_eq!("1.0.x-dev", package.get_pretty_version()); + + let config = make_config(vec![ + ("name", s("B")), + ("version", s("4.x-dev")), + ( + "extra", + map(vec![( + "branch-alias", + map(vec![("4.x-dev", s("4.0.x-dev"))]), + )]), + ), + ]); + + let package = loader.load(config, None).unwrap(); + + assert!(package.as_complete_alias_package().is_some()); + assert_eq!("4.0.x-dev", package.get_pretty_version()); + + let config = make_config(vec![ + ("name", s("B")), + ("version", s("4.x-dev")), + ( + "extra", + map(vec![("branch-alias", map(vec![("4.x-dev", s("4.0-dev"))]))]), + ), + ]); + + let package = loader.load(config, None).unwrap(); + + assert!(package.as_complete_alias_package().is_some()); + assert_eq!("4.0.x-dev", package.get_pretty_version()); + + let config = make_config(vec![ + ("name", s("C")), + ("version", s("4.x-dev")), + ( + "extra", + map(vec![( + "branch-alias", + map(vec![("4.x-dev", s("3.4.x-dev"))]), + )]), + ), + ]); + + let package = loader.load(config, None).unwrap(); + + assert!(package.as_complete_package().is_some()); + assert_eq!("4.x-dev", package.get_pretty_version()); } +#[ignore] #[test] -#[ignore = "ArrayLoader::load parses constraints via a look-around regex the regex crate cannot compile"] fn test_package_aliasing_without_branch_alias() { - let _loader = set_up(); - todo!() + let loader = set_up(); + // non-numeric gets a default alias + let config = make_config(vec![ + ("name", s("A")), + ("version", s("dev-main")), + ("default-branch", PhpMixed::Bool(true)), + ]); + + let package = loader.load(config, None).unwrap(); + + assert!(package.as_complete_alias_package().is_some()); + assert_eq!( + VersionParser::DEFAULT_BRANCH_ALIAS, + package.get_pretty_version() + ); + + // non-default branch gets no alias even if non-numeric + let config = make_config(vec![ + ("name", s("A")), + ("version", s("dev-main")), + ("default-branch", PhpMixed::Bool(false)), + ]); + + let package = loader.load(config, None).unwrap(); + + assert!(package.as_complete_package().is_some()); + assert_eq!("dev-main", package.get_pretty_version()); + + // default branch gets no alias if already numeric + let config = make_config(vec![ + ("name", s("A")), + ("version", s("2.x-dev")), + ("default-branch", PhpMixed::Bool(true)), + ]); + + let package = loader.load(config, None).unwrap(); + + assert!(package.as_complete_package().is_some()); + assert_eq!("2.9999999.9999999.9999999-dev", package.get_version()); + + // default branch gets no alias if already numeric, with v prefix + let config = make_config(vec![ + ("name", s("A")), + ("version", s("v2.x-dev")), + ("default-branch", PhpMixed::Bool(true)), + ]); + + let package = loader.load(config, None).unwrap(); + + assert!(package.as_complete_package().is_some()); + assert_eq!("2.9999999.9999999.9999999-dev", package.get_version()); } +#[ignore] #[test] -#[ignore = "ArrayLoader::load parses constraints via a look-around regex the regex crate cannot compile"] fn test_abandoned() { - let _loader = set_up(); - todo!() + let loader = set_up(); + let config = make_config(vec![ + ("name", s("A")), + ("version", s("1.2.3.4")), + ("abandoned", s("foo/bar")), + ]); + + let package = loader.load(config, None).unwrap(); + let package = package.as_complete().unwrap(); + assert!(package.is_abandoned()); + assert_eq!( + Some("foo/bar".to_string()), + package.get_replacement_package() + ); } +#[ignore] #[test] -#[ignore = "ArrayLoader::load parses constraints via a look-around regex the regex crate cannot compile"] fn test_not_abandoned() { - let _loader = set_up(); - todo!() + let loader = set_up(); + let config = make_config(vec![("name", s("A")), ("version", s("1.2.3.4"))]); + + let package = loader.load(config, None).unwrap(); + let package = package.as_complete().unwrap(); + assert!(!package.is_abandoned()); +} + +/// ref: ArrayLoaderTest::providePluginApiVersions +fn provide_plugin_api_versions() -> Vec<&'static str> { + vec![ + "1.0", + "1.0.0", + "1.0.0.0", + "1", + "=1.0.0", + "==1.0", + "~1.0.0", + "*", + "3.0.*", + "@stable", + "1.0.0@stable", + "^5.1", + ">=1.0.0 <2.5", + "x", + "1.0.0-dev", + ] } +#[ignore] #[test] -#[ignore = "ArrayLoader::load parses constraints via a look-around regex the regex crate cannot compile"] fn test_plugin_api_version_are_kept_as_declared() { - let _loader = set_up(); - todo!() + let loader = set_up(); + for api_version in provide_plugin_api_versions() { + let links = loader + .parse_links( + "Plugin", + "9.9.9", + Link::TYPE_REQUIRE, + make_config(vec![("composer-plugin-api", s(api_version))]), + ) + .unwrap(); + + assert!(links.contains_key("composer-plugin-api")); + assert_eq!( + api_version, + links + .get("composer-plugin-api") + .unwrap() + .get_constraint() + .get_pretty_string() + ); + } } +#[ignore] #[test] -#[ignore = "ArrayLoader::load parses constraints via a look-around regex the regex crate cannot compile"] fn test_plugin_api_version_does_support_self_version() { - let _loader = set_up(); - todo!() + let loader = set_up(); + let links = loader + .parse_links( + "Plugin", + "6.6.6", + Link::TYPE_REQUIRE, + make_config(vec![("composer-plugin-api", s("self.version"))]), + ) + .unwrap(); + + assert!(links.contains_key("composer-plugin-api")); + assert_eq!( + "6.6.6", + links + .get("composer-plugin-api") + .unwrap() + .get_constraint() + .get_pretty_string() + ); } +#[ignore] #[test] -#[ignore = "ArrayLoader::load parses constraints via a look-around regex the regex crate cannot compile"] fn test_parse_links_integer_target() { - let _loader = set_up(); - todo!() + let loader = set_up(); + let links = loader + .parse_links( + "Plugin", + "9.9.9", + Link::TYPE_REQUIRE, + make_config(vec![("1", s("dev-main"))]), + ) + .unwrap(); + + assert!(links.contains_key("1")); } +#[ignore] #[test] -#[ignore = "ArrayLoader::load parses constraints via a look-around regex the regex crate cannot compile"] fn test_parse_links_invalid_version() { - let _loader = set_up(); - todo!() + let loader = set_up(); + let err = loader + .parse_links( + "Plugin", + "9.9.9", + Link::TYPE_REQUIRE, + make_config(vec![("composer-plugin-api", s("^^^"))]), + ) + .unwrap_err(); + assert_eq!( + "Link constraint in Plugin requires > composer-plugin-api should be a valid version constraint, got \"^^^\"", + err.to_string() + ); } +#[ignore] #[test] -#[ignore = "ArrayLoader::load parses constraints via a look-around regex the regex crate cannot compile"] fn test_none_string_version() { - let _loader = set_up(); - todo!() + let loader = set_up(); + let config = make_config(vec![ + ("name", s("acme/package")), + ("version", PhpMixed::Int(1)), + ]); + + let package = loader.load(config, None).unwrap(); + assert_eq!("1", package.get_pretty_version()); } +#[ignore] #[test] -#[ignore = "ArrayLoader::load parses constraints via a look-around regex the regex crate cannot compile"] fn test_none_string_source_dist_reference() { - let _loader = set_up(); - todo!() + let loader = set_up(); + let config = make_config(vec![ + ("name", s("acme/package")), + ("version", s("dev-main")), + ( + "source", + map(vec![ + ("type", s("svn")), + ("url", s("https://example.org/")), + ("reference", PhpMixed::Int(2019)), + ]), + ), + ( + "dist", + map(vec![ + ("type", s("zip")), + ("url", s("https://example.org/")), + ("reference", PhpMixed::Int(2019)), + ]), + ), + ]); + + let package = loader.load(config, None).unwrap(); + assert_eq!(Some("2019".to_string()), package.get_source_reference()); + assert_eq!(Some("2019".to_string()), package.get_dist_reference()); } +#[ignore] #[test] -#[ignore = "ArrayLoader::load parses constraints via a look-around regex the regex crate cannot compile"] fn test_branch_alias_integer_index() { - let _loader = set_up(); - todo!() + let loader = set_up(); + let config = make_config(vec![ + ("name", s("acme/package")), + ("version", s("dev-1")), + ( + "extra", + map(vec![("branch-alias", map(vec![("1", s("1.3-dev"))]))]), + ), + ( + "dist", + map(vec![("type", s("zip")), ("url", s("https://example.org/"))]), + ), + ]); + + assert_eq!(None, loader.get_branch_alias(&config).unwrap()); } +#[ignore] #[test] -#[ignore = "ArrayLoader::load parses constraints via a look-around regex the regex crate cannot compile"] fn test_package_links_require() { - let _loader = set_up(); - todo!() + let loader = set_up(); + let config = make_config(vec![ + ("name", s("acme/package")), + ("version", s("dev-1")), + ("require", map(vec![("foo/bar", s("1.0"))])), + ]); + + let package = loader.load(config, None).unwrap(); + assert!(package.get_requires().contains_key("foo/bar")); + assert_eq!( + "1.0", + package + .get_requires() + .get("foo/bar") + .unwrap() + .get_constraint() + .get_pretty_string() + ); } +#[ignore] #[test] -#[ignore = "ArrayLoader::load parses constraints via a look-around regex the regex crate cannot compile"] fn test_package_links_require_invalid() { - let _loader = set_up(); - todo!() + let loader = set_up(); + let config = make_config(vec![ + ("name", s("acme/package")), + ("version", s("dev-1")), + ( + "require", + map(vec![("foo/bar", map(vec![("random-string", s("1.0"))]))]), + ), + ]); + + let package = loader.load(config, None).unwrap(); + assert_eq!(0, package.get_requires().len()); } +#[ignore] #[test] -#[ignore = "ArrayLoader::load parses constraints via a look-around regex the regex crate cannot compile"] fn test_package_links_replace() { - let _loader = set_up(); - todo!() + let loader = set_up(); + let config = make_config(vec![ + ("name", s("acme/package")), + ("version", s("dev-1")), + ("replace", map(vec![("coyote/package", s("self.version"))])), + ]); + + let package = loader.load(config, None).unwrap(); + assert!(package.get_replaces().contains_key("coyote/package")); + assert_eq!( + "dev-1", + package + .get_replaces() + .get("coyote/package") + .unwrap() + .get_constraint() + .get_pretty_string() + ); } +#[ignore] #[test] -#[ignore = "ArrayLoader::load parses constraints via a look-around regex the regex crate cannot compile"] fn test_package_links_replace_invalid() { - let _loader = set_up(); - todo!() + let loader = set_up(); + let config = make_config(vec![ + ("name", s("acme/package")), + ("version", s("dev-1")), + ("replace", s("coyote/package")), + ]); + + let package = loader.load(config, None).unwrap(); + assert_eq!(0, package.get_replaces().len()); } +#[ignore] #[test] -#[ignore = "ArrayLoader::load parses constraints via a look-around regex the regex crate cannot compile"] fn test_support_string_value() { - let _loader = set_up(); - todo!() + let loader = set_up(); + let config = make_config(vec![ + ("name", s("acme/package")), + ("version", s("dev-1")), + ("support", s("https://example.org")), + ]); + + let package = loader.load(config, None).unwrap(); + let package = package.as_complete().unwrap(); + assert_eq!(0, package.get_support().len()); } +#[ignore] #[test] -#[ignore = "ArrayLoader::load parses constraints via a look-around regex the regex crate cannot compile"] fn test_invalid_version() { - let _loader = set_up(); - todo!() + let loader = set_up(); + let config = make_config(vec![("name", s("acme/package")), ("version", s("AA"))]); + + let err = loader.load(config, None).unwrap_err(); + assert_eq!( + "Failed to normalize version for package \"acme/package\": Invalid version string \"AA\"", + err.to_string() + ); } diff --git a/crates/shirabe/tests/package/loader/root_package_loader_test.rs b/crates/shirabe/tests/package/loader/root_package_loader_test.rs index 82d60ba..e78967e 100644 --- a/crates/shirabe/tests/package/loader/root_package_loader_test.rs +++ b/crates/shirabe/tests/package/loader/root_package_loader_test.rs @@ -4,32 +4,157 @@ // ProcessExecutor / VersionGuesser or require constraints whose parsing goes through a // look-around regex the regex crate cannot compile. +use std::cell::RefCell; +use std::rc::Rc; + +use indexmap::IndexMap; +use shirabe::config::Config; +use shirabe::io::IOInterface; +use shirabe::io::null_io::NullIO; +use shirabe::package::loader::RootPackageLoader; +use shirabe::package::version::{VersionGuesser, VersionParser}; +use shirabe::package::{STABILITY_ALPHA, STABILITY_DEV, STABILITY_RC}; +use shirabe::repository::RepositoryManager; +use shirabe::util::http_downloader::HttpDownloader; +use shirabe::util::process_executor::ProcessExecutor; +use shirabe_php_shim::PhpMixed; + +fn null_io() -> Rc<RefCell<dyn IOInterface>> { + Rc::new(RefCell::new(NullIO::new())) +} + +fn http_downloader( + io: &Rc<RefCell<dyn IOInterface>>, + config: &Rc<RefCell<Config>>, +) -> Rc<RefCell<HttpDownloader>> { + Rc::new(RefCell::new(HttpDownloader::new( + io.clone(), + config.clone(), + IndexMap::new(), + true, + ))) +} + #[test] -#[ignore = "RootPackageLoader::load parses require constraints via a look-around regex; mocks RepositoryManager"] +#[ignore] fn test_stability_flags_parsing() { - todo!() + let io = null_io(); + let config = Rc::new(RefCell::new(Config::new(true, None))); + { + let mut cfg = IndexMap::new(); + cfg.insert( + "repositories".to_string(), + PhpMixed::Array({ + let mut m = IndexMap::new(); + m.insert("packagist".to_string(), PhpMixed::Bool(false)); + m + }), + ); + config.borrow_mut().merge(&cfg, "test"); + } + + let manager = Rc::new(RefCell::new(RepositoryManager::new( + io.clone(), + config.clone(), + http_downloader(&io, &config), + None, + None, + ))); + + let mut process_executor = ProcessExecutor::new(Some(io.clone())); + process_executor.enable_async(); + let guesser = VersionGuesser::new( + config.clone(), + Rc::new(RefCell::new(process_executor)), + VersionParser::new(), + Some(io.clone()), + ); + + let mut loader = RootPackageLoader::new(manager, config.clone(), None, Some(guesser), None); + + let mut data = IndexMap::new(); + data.insert( + "require".to_string(), + PhpMixed::Array({ + let mut m = IndexMap::new(); + m.insert( + "foo/bar".to_string(), + PhpMixed::String("~2.1.0-beta2".to_string()), + ); + m.insert( + "bar/baz".to_string(), + PhpMixed::String("1.0.x-dev as 1.2.0".to_string()), + ); + m.insert( + "qux/quux".to_string(), + PhpMixed::String("1.0.*@rc".to_string()), + ); + m.insert( + "zux/complex".to_string(), + PhpMixed::String("~1.0,>=1.0.2@dev".to_string()), + ); + m.insert( + "or/op".to_string(), + PhpMixed::String("^2.0@dev || ^2.0@dev".to_string()), + ); + m.insert( + "multi/lowest-wins".to_string(), + PhpMixed::String("^2.0@rc || >=3.0@dev , ~3.5@alpha".to_string()), + ); + m.insert( + "or/op-without-flags".to_string(), + PhpMixed::String("dev-master || 2.0 , ~3.5-alpha".to_string()), + ); + m.insert( + "or/op-without-flags2".to_string(), + PhpMixed::String("3.0-beta || 2.0 , ~3.5-alpha".to_string()), + ); + m + }), + ); + data.insert( + "minimum-stability".to_string(), + PhpMixed::String("alpha".to_string()), + ); + + let package = loader + .load(data, "Composer\\Package\\RootPackage", None) + .unwrap(); + let package = package.as_root().unwrap(); + + assert_eq!("alpha", package.get_minimum_stability()); + + let mut expected = IndexMap::new(); + expected.insert("bar/baz".to_string(), STABILITY_DEV); + expected.insert("qux/quux".to_string(), STABILITY_RC); + expected.insert("zux/complex".to_string(), STABILITY_DEV); + expected.insert("or/op".to_string(), STABILITY_DEV); + expected.insert("multi/lowest-wins".to_string(), STABILITY_DEV); + expected.insert("or/op-without-flags".to_string(), STABILITY_DEV); + expected.insert("or/op-without-flags2".to_string(), STABILITY_ALPHA); + assert_eq!(expected, package.get_stability_flags()); } #[test] -#[ignore = "mocks RepositoryManager and a ProcessExecutor returning a non-zero git result"] +#[ignore = "requires getProcessExecutorMock with expects(['return' => 1]); no ProcessExecutorMock mocking infrastructure exists"] fn test_no_version_is_visible_in_pretty_version() { todo!() } #[test] -#[ignore = "mocks RepositoryManager and a VersionGuesser returning a fixed guessed version"] +#[ignore = "requires getMockBuilder VersionGuesser mock with guessVersion expectation; no VersionGuesser mocking infrastructure exists"] fn test_pretty_version_for_root_package_in_version_branch() { todo!() } #[test] -#[ignore = "mocks RepositoryManager and a ProcessExecutor feeding git branch output"] +#[ignore = "requires getProcessExecutorMock with expects() git command expectations; no ProcessExecutorMock mocking infrastructure exists"] fn test_feature_branch_pretty_version() { todo!() } #[test] -#[ignore = "mocks RepositoryManager and a ProcessExecutor feeding git branch output"] +#[ignore = "requires getProcessExecutorMock with expects() git command expectations; no ProcessExecutorMock mocking infrastructure exists"] fn test_non_feature_branch_pretty_version() { todo!() } diff --git a/crates/shirabe/tests/package/loader/validating_array_loader_test.rs b/crates/shirabe/tests/package/loader/validating_array_loader_test.rs index 9858df1..db5ff07 100644 --- a/crates/shirabe/tests/package/loader/validating_array_loader_test.rs +++ b/crates/shirabe/tests/package/loader/validating_array_loader_test.rs @@ -1,27 +1,1029 @@ //! ref: composer/tests/Composer/Test/Package/Loader/ValidatingArrayLoaderTest.php -// ValidatingArrayLoader wraps ArrayLoader, whose constraint parsing uses a look-around -// regex the regex crate cannot compile; the success/warning data sets are large. +use std::cell::RefCell; +use std::rc::Rc; + +use indexmap::IndexMap; +use shirabe::package::handle::PackageInterfaceHandle; +use shirabe::package::loader::{InvalidPackageException, LoaderInterface, ValidatingArrayLoader}; +use shirabe_php_shim::PhpMixed; + +#[path = "../../common/test_case.rs"] +mod test_case; + +fn s(v: &str) -> PhpMixed { + PhpMixed::String(v.to_string()) +} + +fn i(v: i64) -> PhpMixed { + PhpMixed::Int(v) +} + +fn b(v: bool) -> PhpMixed { + PhpMixed::Bool(v) +} + +/// Build a PHP assoc array (string-keyed). The impl reads every PHP `array`, +/// list or assoc, via `PhpMixed::as_array`, so both shapes are `PhpMixed::Array`. +fn arr(entries: Vec<(&str, PhpMixed)>) -> PhpMixed { + let mut m = IndexMap::new(); + for (k, v) in entries { + m.insert(k.to_string(), v); + } + PhpMixed::Array(m) +} + +/// Build a PHP list array as a `PhpMixed::Array` with sequential string keys. +fn list(items: Vec<PhpMixed>) -> PhpMixed { + let mut m = IndexMap::new(); + for (idx, v) in items.into_iter().enumerate() { + m.insert(idx.to_string(), v); + } + PhpMixed::Array(m) +} + +fn config(entries: Vec<(&str, PhpMixed)>) -> IndexMap<String, PhpMixed> { + let mut m = IndexMap::new(); + for (k, v) in entries { + m.insert(k.to_string(), v); + } + m +} + +type Calls = Rc<RefCell<Vec<IndexMap<String, PhpMixed>>>>; + +/// Mock LoaderInterface recording every `load` invocation, mirroring PHPUnit's +/// `expects($this->once())->method('load')->with(...)`. +#[derive(Debug)] +struct MockLoader { + calls: Calls, +} + +impl MockLoader { + fn new() -> (Self, Calls) { + let calls: Calls = Rc::new(RefCell::new(Vec::new())); + ( + MockLoader { + calls: calls.clone(), + }, + calls, + ) + } +} + +impl LoaderInterface for MockLoader { + fn load( + &self, + config: IndexMap<String, PhpMixed>, + _class: Option<String>, + ) -> anyhow::Result<PackageInterfaceHandle> { + self.calls.borrow_mut().push(config); + Ok(test_case::get_package("mock/mock", "1.0.0")) + } + + fn as_any(&self) -> &dyn std::any::Any { + self + } +} + +fn invalid_naming_error(name: &str) -> Vec<String> { + vec*/[a-z0-9](([_.]?|-{{0,2}})[a-z0-9]+)*$\".", + name + )] +} + +/// ref: ValidatingArrayLoaderTest::successProvider +fn success_provider() -> Vec<IndexMap<String, PhpMixed>> { + vec![ + // minimal + config(vec![("name", s("foo/bar"))]), + // complete + config(vec![ + ("name", s("foo/bar")), + ("description", s("Foo bar")), + ("version", s("1.0.0")), + ("type", s("library")), + ( + "keywords", + list(vec![s("a"), s("b_c"), s("D E"), s("éîüø"), s("微信")]), + ), + ("homepage", s("https://foo.com")), + ("time", s("2010-10-10T10:10:10+00:00")), + ("license", list(vec![s("MIT"), s("WTFPL")])), + ( + "authors", + list(vec![ + arr(vec![ + ("name", s("Alice")), + ("email", s("alice@example.org")), + ("role", s("Lead")), + ("homepage", s("http://example.org")), + ]), + arr(vec![("name", s("Bob")), ("homepage", s(""))]), + ]), + ), + ( + "support", + arr(vec![ + ("email", s("mail@example.org")), + ("issues", s("http://example.org/")), + ("forum", s("http://example.org/")), + ("wiki", s("http://example.org/")), + ("source", s("http://example.org/")), + ("irc", s("irc://example.org/example")), + ("rss", s("http://example.org/rss")), + ("chat", s("http://example.org/chat")), + ("security", s("https://example.org/security")), + ]), + ), + ( + "funding", + list(vec![ + arr(vec![ + ("type", s("example")), + ("url", s("https://example.org/fund")), + ]), + arr(vec![("url", s("https://example.org/fund"))]), + ]), + ), + ( + "require", + arr(vec![ + ("a/b", s("1.*")), + ("b/c", s("~2")), + ("example/pkg", s(">2.0-dev,<2.4-dev")), + ("composer-runtime-api", s("*")), + ]), + ), + ( + "require-dev", + arr(vec![ + ("a/b", s("1.*")), + ("b/c", s("*")), + ("example/pkg", s(">2.0-dev,<2.4-dev")), + ]), + ), + ( + "conflict", + arr(vec![ + ("a/bx", s("1.*")), + ("b/cx", s(">2.7")), + ("example/pkgx", s(">2.0-dev,<2.4-dev")), + ]), + ), + ( + "replace", + arr(vec![ + ("a/b", s("1.*")), + ("example/pkg", s(">2.0-dev,<2.4-dev")), + ]), + ), + ( + "provide", + arr(vec![ + ("a/b", s("1.*")), + ("example/pkg", s(">2.0-dev,<2.4-dev")), + ]), + ), + ( + "suggest", + arr(vec![("foo/bar", s("Foo bar is very useful"))]), + ), + ( + "autoload", + arr(vec![ + ( + "psr-0", + arr(vec![("Foo\\Bar", s("src/")), ("", s("fallback/libs/"))]), + ), + ("classmap", list(vec![s("dir/"), s("dir2/file.php")])), + ("files", list(vec![s("functions.php")])), + ]), + ), + ("include-path", list(vec![s("lib/")])), + ("target-dir", s("Foo/Bar")), + ("minimum-stability", s("dev")), + ( + "repositories", + list(vec![arr(vec![ + ("type", s("composer")), + ("url", s("https://repo.packagist.org/")), + ])]), + ), + ( + "config", + arr(vec![ + ("bin-dir", s("bin")), + ("vendor-dir", s("vendor")), + ("process-timeout", i(10000)), + ]), + ), + ( + "archive", + arr(vec![( + "exclude", + list(vec![s("/foo/bar"), s("baz"), s("!/foo/bar/baz")]), + )]), + ), + ( + "scripts", + arr(vec![ + ("post-update-cmd", s("Foo\\Bar\\Baz::doSomething")), + ( + "post-install-cmd", + list(vec![s("Foo\\Bar\\Baz::doSomething")]), + ), + ]), + ), + ( + "extra", + arr(vec![ + ( + "random", + arr(vec![("stuff", arr(vec![("deeply", s("nested"))]))]), + ), + ( + "branch-alias", + arr(vec![ + ("dev-master", s("2.0-dev")), + ("dev-old", s("1.0.x-dev")), + ("3.x-dev", s("3.1.x-dev")), + ]), + ), + ]), + ), + ("bin", list(vec![s("bin/foo"), s("bin/bar")])), + ( + "transport-options", + arr(vec![( + "ssl", + arr(vec![("local_cert", s("/opt/certs/test.pem"))]), + )]), + ), + ]), + // test bin as string + config(vec![("name", s("foo/bar")), ("bin", s("bin1"))]), + // package name with dashes + config(vec![("name", s("foo/bar-baz"))]), + config(vec![("name", s("foo/bar--baz"))]), + config(vec![("name", s("foo/b-ar--ba-z"))]), + config(vec![("name", s("npm-asset/angular--core"))]), + // refs as int or string + config(vec![ + ("name", s("foo/bar")), + ( + "source", + arr(vec![ + ("url", s("https://example.org")), + ("reference", i(1234)), + ("type", s("baz")), + ]), + ), + ( + "dist", + arr(vec![ + ("url", s("https://example.org")), + ("reference", s("foobar")), + ("type", s("baz")), + ]), + ), + ]), + // valid php-ext configuration + config(vec![ + ("name", s("foo/bar")), + ("type", s("php-ext")), + ( + "php-ext", + arr(vec![ + ("extension-name", s("ext-xdebug")), + ("priority", i(80)), + ("support-zts", b(true)), + ("support-nts", b(false)), + ("build-path", s("my-extension-source")), + ("download-url-method", s("composer-default")), + ("os-families", list(vec![s("linux"), s("darwin")])), + ( + "configure-options", + list(vec![ + arr(vec![ + ("name", s("enable-xdebug")), + ("needs-value", b(false)), + ("description", s("Enable xdebug support")), + ]), + arr(vec![ + ("name", s("with-xdebug-path")), + ("needs-value", b(true)), + ]), + ]), + ), + ]), + ), + ]), + // valid php-ext with os-families-exclude + config(vec![ + ("name", s("foo/bar")), + ("type", s("php-ext-zend")), + ( + "php-ext", + arr(vec![("os-families-exclude", list(vec![s("windows")]))]), + ), + ]), + // valid php-ext with null build-path + config(vec![ + ("name", s("foo/bar")), + ("type", s("php-ext")), + ("php-ext", arr(vec![("build-path", PhpMixed::Null)])), + ]), + // valid php-ext with one download-url-method in a list + config(vec![ + ("name", s("foo/bar")), + ("type", s("php-ext")), + ( + "php-ext", + arr(vec![( + "download-url-method", + list(vec![s("pre-packaged-binary")]), + )]), + ), + ]), + // valid php-ext with multiple download-url-methods + config(vec![ + ("name", s("foo/bar")), + ("type", s("php-ext")), + ( + "php-ext", + arr(vec![( + "download-url-method", + list(vec![ + s("pre-packaged-binary"), + s("pre-packaged-source"), + s("composer-default"), + ]), + )]), + ), + ]), + ] +} + +/// ref: ValidatingArrayLoaderTest::testLoadSuccess +#[ignore] #[test] -#[ignore = "ValidatingArrayLoader -> ArrayLoader parses constraints via a look-around regex the regex crate cannot compile"] fn test_load_success() { - todo!() + for cfg in success_provider() { + let (internal_loader, _calls) = MockLoader::new(); + let mut loader = ValidatingArrayLoader::new( + Box::new(internal_loader), + true, + None, + ValidatingArrayLoader::CHECK_ALL, + ); + loader + .load(cfg, "Composer\\Package\\CompletePackage") + .unwrap(); + } } +/// ref: ValidatingArrayLoaderTest::errorProvider +fn error_provider() -> Vec<(IndexMap<String, PhpMixed>, Vec<String>)> { + let mut data: Vec<(IndexMap<String, PhpMixed>, Vec<String>)> = Vec::new(); + + for invalid_name in ["foo", "foo/-bar-", "foo/-bar"] { + data.push(( + config(vec![("name", s(invalid_name))]), + invalid_naming_error(invalid_name), + )); + } + for invalid_name in [ + "fo--oo/bar", + "fo-oo/bar__baz", + "fo-oo/bar_.baz", + "foo/bar---baz", + ] { + data.push(( + config(vec![("name", s(invalid_name))]), + invalid_naming_error(invalid_name), + )); + } + + data.push(( + config(vec![("name", s("foo/bar")), ("homepage", i(43))]), + vec!["homepage : should be a string, int given".to_string()], + )); + data.push(( + config(vec![ + ("name", s("foo/bar")), + ("support", arr(vec![("source", list(vec![]))])), + ]), + vec!["support.source : invalid value, must be a string".to_string()], + )); + data.push(( + config(vec![("name", s("foo/bar.json"))]), + vec!["name : foo/bar.json is invalid, package names can not end in .json, consider renaming it or perhaps using a -json suffix instead.".to_string()], + )); + data.push(( + config(vec![("name", s("com1/foo"))]), + vec!["name : com1/foo is reserved, package and vendor names can not match any of: nul, con, prn, aux, com1, com2, com3, com4, com5, com6, com7, com8, com9, lpt1, lpt2, lpt3, lpt4, lpt5, lpt6, lpt7, lpt8, lpt9.".to_string()], + )); + data.push(( + config(vec![("name", s("Foo/Bar"))]), + vec!["name : Foo/Bar is invalid, it should not contain uppercase characters. We suggest using foo/bar instead.".to_string()], + )); + data.push(( + config(vec![("name", s("foo/bar")), ("autoload", s("strings"))]), + vec!["autoload : should be an array, string given".to_string()], + )); + data.push(( + config(vec![ + ("name", s("foo/bar")), + ("autoload", arr(vec![("psr0", arr(vec![("foo", s("src"))]))])), + ]), + vec!["autoload : invalid value (psr0), must be one of psr-0, psr-4, classmap, files, exclude-from-classmap".to_string()], + )); + data.push(( + config(vec![ + ("name", s("foo/bar")), + ("transport-options", s("test")), + ]), + vec!["transport-options : should be an array, string given".to_string()], + )); + data.push(( + config(vec![ + ("name", s("foo/bar")), + ( + "source", + arr(vec![ + ("url", s("--foo")), + ("reference", s(" --bar")), + ("type", s("baz")), + ]), + ), + ( + "dist", + arr(vec![ + ("url", s(" --foox")), + ("reference", s("--barx")), + ("type", s("baz")), + ]), + ), + ]), + vec![ + "dist.reference : must not start with a \"-\", \"--barx\" given".to_string(), + "dist.url : must not start with a \"-\", \" --foox\" given".to_string(), + "source.reference : must not start with a \"-\", \" --bar\" given".to_string(), + "source.url : must not start with a \"-\", \"--foo\" given".to_string(), + ], + )); + data.push(( + config(vec![ + ("name", s("foo/bar")), + ("require", arr(vec![("foo/Bar", s("1.*"))])), + ]), + vec!["require.foo/Bar : a package cannot set a require on itself".to_string()], + )); + data.push(( + config(vec![ + ("name", s("foo/bar")), + ("source", arr(vec![("url", i(1))])), + ("dist", arr(vec![("url", PhpMixed::Null)])), + ]), + vec![ + "source.type : must be present".to_string(), + "source.url : should be a string, int given".to_string(), + "source.reference : must be present".to_string(), + "dist.type : must be present".to_string(), + "dist.url : must be present".to_string(), + ], + )); + data.push(( + config(vec![ + ("name", s("foo/bar")), + ("replace", list(vec![s("acme/bar")])), + ]), + vec!["replace.0 : invalid version constraint (Could not parse version constraint acme/bar: Invalid version string \"acme/bar\")".to_string()], + )); + data.push(( + config(vec![("require", arr(vec![("acme/bar", s("^1.0"))]))]), + vec!["name : must be present".to_string()], + )); + data.push(( + config(vec![ + ("name", s("foo/bar")), + ("type", s("library")), + ("php-ext", arr(vec![("extension-name", s("ext-foobar"))])), + ]), + vec!["php-ext can only be set by packages of type \"php-ext\" or \"php-ext-zend\" which must be C extensions".to_string()], + )); + data.push(( + config(vec![ + ("name", s("foo/bar")), + ("type", s("php-ext")), + ("php-ext", arr(vec![("extension-name", i(123))])), + ]), + vec!["php-ext.extension-name : should be a string, int given".to_string()], + )); + data.push(( + config(vec![ + ("name", s("foo/bar")), + ("type", s("php-ext")), + ("php-ext", arr(vec![("priority", s("invalid"))])), + ]), + vec!["php-ext.priority : should be an integer, string given".to_string()], + )); + data.push(( + config(vec![ + ("name", s("foo/bar")), + ("type", s("php-ext")), + ("php-ext", arr(vec![("support-zts", s("yes"))])), + ]), + vec!["php-ext.support-zts : should be a boolean, string given".to_string()], + )); + data.push(( + config(vec![ + ("name", s("foo/bar")), + ("type", s("php-ext")), + ("php-ext", arr(vec![("support-nts", i(1))])), + ]), + vec!["php-ext.support-nts : should be a boolean, int given".to_string()], + )); + data.push(( + config(vec![ + ("name", s("foo/bar")), + ("type", s("php-ext")), + ("php-ext", arr(vec![("build-path", i(123))])), + ]), + vec!["php-ext.build-path : should be a string or null, int given".to_string()], + )); + data.push(( + config(vec![ + ("name", s("foo/bar")), + ("type", s("php-ext")), + ("php-ext", arr(vec![("download-url-method", i(123))])), + ]), + vec!["php-ext.download-url-method : should be an array or a string, int given".to_string()], + )); + data.push(( + config(vec![ + ("name", s("foo/bar")), + ("type", s("php-ext")), + ( + "php-ext", + arr(vec![("download-url-method", s("invalid-method"))]), + ), + ]), + vec!["php-ext.download-url-method.0 : invalid value (invalid-method), must be one of composer-default, pre-packaged-source, pre-packaged-binary".to_string()], + )); + data.push(( + config(vec![ + ("name", s("foo/bar")), + ("type", s("php-ext")), + ("php-ext", arr(vec![("download-url-method", list(vec![]))])), + ]), + vec!["php-ext.download-url-method : must contain at least one element".to_string()], + )); + data.push(( + config(vec![ + ("name", s("foo/bar")), + ("type", s("php-ext")), + ( + "php-ext", + arr(vec![( + "download-url-method", + list(vec![i(1), b(true), list(vec![])]), + )]), + ), + ]), + vec![ + "php-ext.download-url-method.0 : should be a string, int given".to_string(), + "php-ext.download-url-method.1 : should be a string, bool given".to_string(), + "php-ext.download-url-method.2 : should be a string, array given".to_string(), + ], + )); + data.push(( + config(vec![ + ("name", s("foo/bar")), + ("type", s("php-ext")), + ( + "php-ext", + arr(vec![( + "download-url-method", + list(vec![s("invalid-method"), s("composer-default")]), + )]), + ), + ]), + vec!["php-ext.download-url-method.0 : invalid value (invalid-method), must be one of composer-default, pre-packaged-source, pre-packaged-binary".to_string()], + )); + data.push(( + config(vec![ + ("name", s("foo/bar")), + ("type", s("php-ext")), + ( + "php-ext", + arr(vec![( + "download-url-method", + list(vec![s("invalid-method"), s("another-invalid-method")]), + )]), + ), + ]), + vec![ + "php-ext.download-url-method.0 : invalid value (invalid-method), must be one of composer-default, pre-packaged-source, pre-packaged-binary".to_string(), + "php-ext.download-url-method.1 : invalid value (another-invalid-method), must be one of composer-default, pre-packaged-source, pre-packaged-binary".to_string(), + ], + )); + data.push(( + config(vec![ + ("name", s("foo/bar")), + ("type", s("php-ext")), + ( + "php-ext", + arr(vec![ + ("os-families", list(vec![s("linux")])), + ("os-families-exclude", list(vec![s("windows")])), + ]), + ), + ]), + vec!["php-ext : os-families and os-families-exclude cannot both be specified".to_string()], + )); + data.push(( + config(vec![ + ("name", s("foo/bar")), + ("type", s("php-ext")), + ("php-ext", arr(vec![("os-families", s("linux"))])), + ]), + vec!["php-ext.os-families : should be an array, string given".to_string()], + )); + data.push(( + config(vec![ + ("name", s("foo/bar")), + ("type", s("php-ext")), + ("php-ext", arr(vec![("os-families", list(vec![]))])), + ]), + vec!["php-ext.os-families : must contain at least one element".to_string()], + )); + data.push(( + config(vec![ + ("name", s("foo/bar")), + ("type", s("php-ext")), + ( + "php-ext", + arr(vec![("os-families", list(vec![s("invalid-os"), s("linux")]))]), + ), + ]), + vec!["php-ext.os-families.0 : invalid value (invalid-os), must be one of windows, bsd, darwin, solaris, linux, unknown".to_string()], + )); + data.push(( + config(vec![ + ("name", s("foo/bar")), + ("type", s("php-ext")), + ("php-ext", arr(vec![("os-families", list(vec![i(123)]))])), + ]), + vec!["php-ext.os-families.0 : should be a string, int given".to_string()], + )); + data.push(( + config(vec![ + ("name", s("foo/bar")), + ("type", s("php-ext")), + ("php-ext", arr(vec![("os-families-exclude", s("windows"))])), + ]), + vec!["php-ext.os-families-exclude : should be an array, string given".to_string()], + )); + data.push(( + config(vec![ + ("name", s("foo/bar")), + ("type", s("php-ext")), + ("php-ext", arr(vec![("os-families-exclude", list(vec![]))])), + ]), + vec!["php-ext.os-families-exclude : must contain at least one element".to_string()], + )); + data.push(( + config(vec![ + ("name", s("foo/bar")), + ("type", s("php-ext")), + ( + "php-ext", + arr(vec![("os-families-exclude", list(vec![s("invalid")]))]), + ), + ]), + vec!["php-ext.os-families-exclude.0 : invalid value (invalid), must be one of windows, bsd, darwin, solaris, linux, unknown".to_string()], + )); + data.push(( + config(vec![ + ("name", s("foo/bar")), + ("type", s("php-ext")), + ("php-ext", arr(vec![("configure-options", s("invalid"))])), + ]), + vec!["php-ext.configure-options : should be an array, string given".to_string()], + )); + data.push(( + config(vec![ + ("name", s("foo/bar")), + ("type", s("php-ext")), + ( + "php-ext", + arr(vec![("configure-options", list(vec![s("invalid")]))]), + ), + ]), + vec!["php-ext.configure-options.0 : should be an array, string given".to_string()], + )); + data.push(( + config(vec![ + ("name", s("foo/bar")), + ("type", s("php-ext")), + ( + "php-ext", + arr(vec![( + "configure-options", + list(vec![arr(vec![("description", s("test"))])]), + )]), + ), + ]), + vec!["php-ext.configure-options.0.name : must be present".to_string()], + )); + data.push(( + config(vec![ + ("name", s("foo/bar")), + ("type", s("php-ext")), + ( + "php-ext", + arr(vec![( + "configure-options", + list(vec![arr(vec![("name", i(123))])]), + )]), + ), + ]), + vec!["php-ext.configure-options.0.name : should be a string, int given".to_string()], + )); + data.push(( + config(vec![ + ("name", s("foo/bar")), + ("type", s("php-ext")), + ( + "php-ext", + arr(vec![( + "configure-options", + list(vec![arr(vec![ + ("name", s("valid-name")), + ("needs-value", s("yes")), + ])]), + )]), + ), + ]), + vec![ + "php-ext.configure-options.0.needs-value : should be a boolean, string given" + .to_string(), + ], + )); + data.push(( + config(vec![ + ("name", s("foo/bar")), + ("type", s("php-ext")), + ( + "php-ext", + arr(vec![( + "configure-options", + list(vec![arr(vec![ + ("name", s("valid-name")), + ("description", i(123)), + ])]), + )]), + ), + ]), + vec!["php-ext.configure-options.0.description : should be a string, int given".to_string()], + )); + + data +} + +/// ref: ValidatingArrayLoaderTest::testLoadFailureThrowsException +#[ignore] #[test] -#[ignore = "ValidatingArrayLoader -> ArrayLoader parses constraints via a look-around regex the regex crate cannot compile"] fn test_load_failure_throws_exception() { - todo!() + for (cfg, mut expected_errors) in error_provider() { + let (internal_loader, _calls) = MockLoader::new(); + let mut loader = ValidatingArrayLoader::new( + Box::new(internal_loader), + true, + None, + ValidatingArrayLoader::CHECK_ALL, + ); + match loader.load(cfg, "Composer\\Package\\CompletePackage") { + Ok(_) => panic!("Expected exception to be thrown"), + Err(e) => { + let exception = e + .downcast_ref::<InvalidPackageException>() + .expect("Expected InvalidPackageException"); + let mut errors: Vec<String> = exception.get_errors().to_vec(); + expected_errors.sort(); + errors.sort(); + assert_eq!(expected_errors, errors); + } + } + } } +/// ref: ValidatingArrayLoaderTest::warningProvider +/// Returns (config, expected_warnings, must_check, expected_array). +fn warning_provider() -> Vec<( + IndexMap<String, PhpMixed>, + Vec<String>, + bool, + Option<IndexMap<String, PhpMixed>>, +)> { + vec![ + ( + config(vec![("name", s("foo/bar")), ("homepage", s("foo:bar"))]), + vec!["homepage : invalid value (foo:bar), must be an http/https URL".to_string()], + true, + None, + ), + ( + config(vec![ + ("name", s("foo/bar")), + ( + "support", + arr(vec![ + ("source", s("foo:bar")), + ("forum", s("foo:bar")), + ("issues", s("foo:bar")), + ("wiki", s("foo:bar")), + ("chat", s("foo:bar")), + ("security", s("foo:bar")), + ]), + ), + ]), + vec![ + "support.source : invalid value (foo:bar), must be an http/https URL".to_string(), + "support.forum : invalid value (foo:bar), must be an http/https URL".to_string(), + "support.issues : invalid value (foo:bar), must be an http/https URL".to_string(), + "support.wiki : invalid value (foo:bar), must be an http/https URL".to_string(), + "support.chat : invalid value (foo:bar), must be an http/https URL".to_string(), + "support.security : invalid value (foo:bar), must be an http/https URL".to_string(), + ], + true, + None, + ), + ( + config(vec![ + ("name", s("foo/bar")), + ( + "require", + arr(vec![ + ("foo/baz", s("*")), + ("bar/baz", s(">=1.0")), + ("bar/hacked", s("@stable")), + ("bar/woo", s("1.0.0")), + ]), + ), + ]), + vec![ + "require.foo/baz : unbound version constraints (*) should be avoided".to_string(), + "require.bar/baz : unbound version constraints (>=1.0) should be avoided" + .to_string(), + "require.bar/hacked : unbound version constraints (@stable) should be avoided" + .to_string(), + "require.bar/woo : exact version constraints (1.0.0) should be avoided if the package follows semantic versioning".to_string(), + ], + false, + None, + ), + ( + config(vec![ + ("name", s("foo/bar")), + ( + "require", + arr(vec![ + ("foo/baz", s(">1, <0.5")), + ("bar/baz", s("dev-main, >0.5")), + ]), + ), + ]), + vec![ + "require.foo/baz : this version constraint cannot possibly match anything (>1, <0.5)".to_string(), + "require.bar/baz : this version constraint cannot possibly match anything (dev-main, >0.5)".to_string(), + ], + false, + None, + ), + ( + config(vec![ + ("name", s("foo/bar")), + ("require", arr(vec![("bar/unstable", s("0.3.0"))])), + ]), + vec![], + false, + None, + ), + ( + config(vec![ + ("name", s("foo/bar")), + ( + "extra", + arr(vec![( + "branch-alias", + arr(vec![("5.x-dev", s("3.1.x-dev"))]), + )]), + ), + ]), + vec!["extra.branch-alias.5.x-dev : the target branch (3.1.x-dev) is not a valid numeric alias for this version".to_string()], + false, + None, + ), + ( + config(vec![ + ("name", s("foo/bar")), + ( + "extra", + arr(vec![("branch-alias", arr(vec![("5.x-dev", s("3.1-dev"))]))]), + ), + ]), + vec!["extra.branch-alias.5.x-dev : the target branch (3.1-dev) is not a valid numeric alias for this version".to_string()], + false, + None, + ), + ( + config(vec![ + ("name", s("foo/bar")), + ("require", arr(vec![("Foo/Baz", s("^1.0"))])), + ]), + vec!["require.Foo/Baz is invalid, it should not contain uppercase characters. Please use foo/baz instead.".to_string()], + false, + None, + ), + ( + config(vec![("name", s("a/b")), ("license", s("XXXXX"))]), + vec![format!( + "License \"XXXXX\" is not a valid SPDX license identifier, see https://spdx.org/licenses/ if you use an open license.{}If the software is closed-source, you may use \"proprietary\" as license.", + shirabe_php_shim::PHP_EOL + )], + true, + Some(config(vec![ + ("name", s("a/b")), + ("license", list(vec![s("XXXXX")])), + ])), + ), + ( + config(vec![ + ("name", s("a/b")), + ("license", list(vec![arr(vec![("author", s("bar"))]), s("MIT")])), + ]), + vec!["License {\"author\":\"bar\"} should be a string.".to_string()], + true, + Some(config(vec![ + ("name", s("a/b")), + ("license", list(vec![s("MIT")])), + ])), + ), + ] +} + +/// ref: ValidatingArrayLoaderTest::testLoadWarnings +#[ignore] #[test] -#[ignore = "ValidatingArrayLoader -> ArrayLoader parses constraints via a look-around regex the regex crate cannot compile"] fn test_load_warnings() { - todo!() + for (cfg, mut expected_warnings, _must_check, _expected_array) in warning_provider() { + let (internal_loader, _calls) = MockLoader::new(); + let mut loader = ValidatingArrayLoader::new( + Box::new(internal_loader), + true, + None, + ValidatingArrayLoader::CHECK_ALL, + ); + loader + .load(cfg, "Composer\\Package\\CompletePackage") + .unwrap(); + let mut warnings: Vec<String> = loader.get_warnings().to_vec(); + expected_warnings.sort(); + warnings.sort(); + assert_eq!(expected_warnings, warnings); + } } +/// ref: ValidatingArrayLoaderTest::testLoadSkipsWarningDataWhenIgnoringErrors +#[ignore] #[test] -#[ignore = "ValidatingArrayLoader -> ArrayLoader parses constraints via a look-around regex the regex crate cannot compile"] fn test_load_skips_warning_data_when_ignoring_errors() { - todo!() + for (mut cfg, _expected_warnings, must_check, expected_array) in warning_provider() { + if !must_check { + assert!(true); + continue; + } + let (internal_loader, calls) = MockLoader::new(); + let expected = expected_array.unwrap_or_else(|| config(vec![("name", s("a/b"))])); + + let mut loader = ValidatingArrayLoader::new( + Box::new(internal_loader), + true, + None, + ValidatingArrayLoader::CHECK_ALL, + ); + cfg.insert("name".to_string(), s("a/b")); + loader + .load(cfg, "Composer\\Package\\CompletePackage") + .unwrap(); + + // The mock recorded exactly the (post-validation) config passed to the inner loader. + let recorded = calls.borrow(); + assert_eq!(recorded.len(), 1); + assert_eq!(recorded[0], expected); + } } diff --git a/crates/shirabe/tests/package/locker_test.rs b/crates/shirabe/tests/package/locker_test.rs index 7b5d39f..c793a8a 100644 --- a/crates/shirabe/tests/package/locker_test.rs +++ b/crates/shirabe/tests/package/locker_test.rs @@ -4,61 +4,61 @@ // mocked ProcessExecutor to drive lock read/write and freshness checks; mocking is not // available here. #[test] -#[ignore = "mocks JsonFile/InstallationManager/repository/ProcessExecutor to drive Locker; mocking is not available"] +#[ignore = "requires PHPUnit mock of JsonFile (exists/read) which is not available"] fn test_is_locked() { todo!() } #[test] -#[ignore = "mocks JsonFile/InstallationManager/repository/ProcessExecutor to drive Locker; mocking is not available"] +#[ignore = "requires PHPUnit mock of JsonFile (exists) which is not available"] fn test_get_not_locked_packages() { todo!() } #[test] -#[ignore = "mocks JsonFile/InstallationManager/repository/ProcessExecutor to drive Locker; mocking is not available"] +#[ignore = "requires PHPUnit mock of JsonFile (exists/read) which is not available"] fn test_get_locked_packages() { todo!() } #[test] -#[ignore = "mocks JsonFile/InstallationManager/repository/ProcessExecutor to drive Locker; mocking is not available"] +#[ignore = "requires PHPUnit mock of JsonFile (write) which is not available"] fn test_set_lock_data() { todo!() } #[test] -#[ignore = "mocks JsonFile/InstallationManager/repository/ProcessExecutor to drive Locker; mocking is not available"] +#[ignore = "requires PHPUnit mock of JsonFile and PackageInterface (createPackageMock) which is not available"] fn test_lock_bad_packages() { todo!() } #[test] -#[ignore = "mocks JsonFile/InstallationManager/repository/ProcessExecutor to drive Locker; mocking is not available"] +#[ignore = "requires PHPUnit mock of JsonFile (read) which is not available"] fn test_is_fresh() { todo!() } #[test] -#[ignore = "mocks JsonFile/InstallationManager/repository/ProcessExecutor to drive Locker; mocking is not available"] +#[ignore = "requires PHPUnit mock of JsonFile (read) which is not available"] fn test_is_fresh_false() { todo!() } #[test] -#[ignore = "mocks JsonFile/InstallationManager/repository/ProcessExecutor to drive Locker; mocking is not available"] +#[ignore = "requires PHPUnit mock of JsonFile (read) which is not available"] fn test_is_fresh_with_content_hash() { todo!() } #[test] -#[ignore = "mocks JsonFile/InstallationManager/repository/ProcessExecutor to drive Locker; mocking is not available"] +#[ignore = "requires PHPUnit mock of JsonFile (read) which is not available"] fn test_is_fresh_with_content_hash_and_no_hash() { todo!() } #[test] -#[ignore = "mocks JsonFile/InstallationManager/repository/ProcessExecutor to drive Locker; mocking is not available"] +#[ignore = "requires PHPUnit mock of JsonFile (read) which is not available"] fn test_is_fresh_false_with_content_hash() { todo!() } diff --git a/crates/shirabe/tests/package/version/version_guesser_test.rs b/crates/shirabe/tests/package/version/version_guesser_test.rs index 75d4ae0..76cd672 100644 --- a/crates/shirabe/tests/package/version/version_guesser_test.rs +++ b/crates/shirabe/tests/package/version/version_guesser_test.rs @@ -1,5 +1,13 @@ //! ref: composer/tests/Composer/Test/Package/Version/VersionGuesserTest.php +use std::cell::RefCell; +use std::rc::Rc; + +use shirabe::config::Config; +use shirabe::package::version::{VersionGuesser, VersionParser}; +use shirabe::util::platform::Platform; +use shirabe::util::process_executor::ProcessExecutor; + #[allow(dead_code)] fn set_up() { // Resets GitUtil's cached `version` static via ReflectionProperty; the static is not @@ -23,94 +31,113 @@ impl Drop for TearDown { // These drive VersionGuesser with a mocked ProcessExecutor feeding git/hg command output; // mocking is not available here. +#[ignore = "requires getProcessExecutorMock with expects() command expectations; no ProcessExecutorMock mocking infrastructure exists"] #[test] -#[ignore = "mocks a ProcessExecutor feeding git/hg output to drive VersionGuesser; mocking is not available"] fn test_hg_guess_version_returns_data() { todo!() } +#[ignore = "requires getProcessExecutorMock with expects() command expectations; no ProcessExecutorMock mocking infrastructure exists"] #[test] -#[ignore = "mocks a ProcessExecutor feeding git/hg output to drive VersionGuesser; mocking is not available"] fn test_guess_version_returns_data() { todo!() } +#[ignore = "requires getProcessExecutorMock with expects() command expectations; no ProcessExecutorMock mocking infrastructure exists"] #[test] -#[ignore = "mocks a ProcessExecutor feeding git/hg output to drive VersionGuesser; mocking is not available"] fn test_guess_version_does_not_see_custom_default_branch_as_non_feature_branch() { todo!() } +#[ignore = "requires getProcessExecutorMock with expects() command expectations; no ProcessExecutorMock mocking infrastructure exists"] #[test] -#[ignore = "mocks a ProcessExecutor feeding git/hg output to drive VersionGuesser; mocking is not available"] fn test_guess_version_reads_and_respects_non_feature_branches_configuration_for_arbitrary_naming() { todo!() } +#[ignore = "requires getProcessExecutorMock with expects() command expectations; no ProcessExecutorMock mocking infrastructure exists"] #[test] -#[ignore = "mocks a ProcessExecutor feeding git/hg output to drive VersionGuesser; mocking is not available"] fn test_guess_version_reads_and_respects_non_feature_branches_configuration_for_arbitrary_naming_regex() { todo!() } +#[ignore = "requires getProcessExecutorMock with expects() command expectations; no ProcessExecutorMock mocking infrastructure exists"] #[test] -#[ignore = "mocks a ProcessExecutor feeding git/hg output to drive VersionGuesser; mocking is not available"] fn test_guess_version_reads_and_respects_non_feature_branches_configuration_for_arbitrary_naming_when_on_non_feature_branch() { todo!() } +#[ignore = "requires getProcessExecutorMock with expects() command expectations; no ProcessExecutorMock mocking infrastructure exists"] #[test] -#[ignore = "mocks a ProcessExecutor feeding git/hg output to drive VersionGuesser; mocking is not available"] fn test_detached_head_becomes_dev_hash() { todo!() } +#[ignore = "requires getProcessExecutorMock with expects() command expectations; no ProcessExecutorMock mocking infrastructure exists"] #[test] -#[ignore = "mocks a ProcessExecutor feeding git/hg output to drive VersionGuesser; mocking is not available"] fn test_detached_fetch_head_becomes_dev_hash_git2() { todo!() } +#[ignore = "requires getProcessExecutorMock with expects() command expectations; no ProcessExecutorMock mocking infrastructure exists"] #[test] -#[ignore = "mocks a ProcessExecutor feeding git/hg output to drive VersionGuesser; mocking is not available"] fn test_detached_commit_head_becomes_dev_hash_git2() { todo!() } +#[ignore = "requires getProcessExecutorMock with expects() command expectations; no ProcessExecutorMock mocking infrastructure exists"] #[test] -#[ignore = "mocks a ProcessExecutor feeding git/hg output to drive VersionGuesser; mocking is not available"] fn test_tag_becomes_version() { todo!() } +#[ignore = "requires getProcessExecutorMock with expects() command expectations; no ProcessExecutorMock mocking infrastructure exists"] #[test] -#[ignore = "mocks a ProcessExecutor feeding git/hg output to drive VersionGuesser; mocking is not available"] fn test_tag_becomes_pretty_version() { todo!() } +#[ignore = "requires getProcessExecutorMock with expects() command expectations; no ProcessExecutorMock mocking infrastructure exists"] #[test] -#[ignore = "mocks a ProcessExecutor feeding git/hg output to drive VersionGuesser; mocking is not available"] fn test_invalid_tag_becomes_version() { todo!() } +#[ignore = "requires getProcessExecutorMock with expects() command expectations; no ProcessExecutorMock mocking infrastructure exists"] #[test] -#[ignore = "mocks a ProcessExecutor feeding git/hg output to drive VersionGuesser; mocking is not available"] fn test_numeric_branches_show_nicely() { todo!() } +#[ignore = "requires getProcessExecutorMock with expects() command expectations; no ProcessExecutorMock mocking infrastructure exists"] #[test] -#[ignore = "mocks a ProcessExecutor feeding git/hg output to drive VersionGuesser; mocking is not available"] fn test_remote_branches_are_selected() { todo!() } +#[ignore] #[test] -#[ignore = "mocks a ProcessExecutor feeding git/hg output to drive VersionGuesser; mocking is not available"] fn test_get_root_version_from_env() { - todo!() + // @dataProvider rootEnvVersionsProvider + let root_env_versions: Vec<(&str, &str)> = vec![ + ("1.0-dev", "1.0.x-dev"), + ("1.0.x-dev", "1.0.x-dev"), + ("1-dev", "1.x-dev"), + ("1.x-dev", "1.x-dev"), + ("1.0.0", "1.0.0"), + ]; + + for (env, expected_version) in root_env_versions { + Platform::put_env("COMPOSER_ROOT_VERSION", env); + let config = Rc::new(RefCell::new(Config::new(true, None))); + let process = Rc::new(RefCell::new(ProcessExecutor::new(None))); + let guesser = VersionGuesser::new(config, process, VersionParser::new(), None); + assert_eq!( + expected_version, + guesser.get_root_version_from_env().unwrap() + ); + Platform::clear_env("COMPOSER_ROOT_VERSION"); + } } diff --git a/crates/shirabe/tests/package/version/version_selector_test.rs b/crates/shirabe/tests/package/version/version_selector_test.rs index fd426ce..fa288cd 100644 --- a/crates/shirabe/tests/package/version/version_selector_test.rs +++ b/crates/shirabe/tests/package/version/version_selector_test.rs @@ -3,79 +3,79 @@ // VersionSelector ranks candidate packages whose versions/constraints are parsed through a // look-around regex the regex crate cannot compile; the setup also mocks a repository. #[test] -#[ignore = "not yet ported (VersionSelector over a mocked repository; constraint parsing uses a look-around regex)"] +#[ignore = "requires mocking RepositorySet::find_packages to return fixed package objects; RepositorySet is a concrete struct with no injectable/overridable find_packages"] fn test_latest_version_is_returned() { todo!() } #[test] -#[ignore = "not yet ported (VersionSelector over a mocked repository; constraint parsing uses a look-around regex)"] +#[ignore = "requires mocking RepositorySet::find_packages to return fixed package objects; RepositorySet is a concrete struct with no injectable/overridable find_packages"] fn test_latest_version_is_returned_that_matches_php_requirements() { todo!() } #[test] -#[ignore = "not yet ported (VersionSelector over a mocked repository; constraint parsing uses a look-around regex)"] +#[ignore = "requires mocking RepositorySet::find_packages to return fixed package objects; RepositorySet is a concrete struct with no injectable/overridable find_packages"] fn test_latest_version_is_returned_that_matches_ext_requirements() { todo!() } #[test] -#[ignore = "not yet ported (VersionSelector over a mocked repository; constraint parsing uses a look-around regex)"] +#[ignore = "requires mocking RepositorySet::find_packages to return fixed package objects; RepositorySet is a concrete struct with no injectable/overridable find_packages"] fn test_latest_version_is_returned_that_matches_platform_ext() { todo!() } #[test] -#[ignore = "not yet ported (VersionSelector over a mocked repository; constraint parsing uses a look-around regex)"] +#[ignore = "requires mocking RepositorySet::find_packages to return fixed package objects; RepositorySet is a concrete struct with no injectable/overridable find_packages"] fn test_latest_version_is_returned_that_matches_composer_requirements() { todo!() } #[test] -#[ignore = "not yet ported (VersionSelector over a mocked repository; constraint parsing uses a look-around regex)"] +#[ignore = "requires mocking RepositorySet::find_packages to return fixed package objects; RepositorySet is a concrete struct with no injectable/overridable find_packages"] fn test_most_stable_version_is_returned() { todo!() } #[test] -#[ignore = "not yet ported (VersionSelector over a mocked repository; constraint parsing uses a look-around regex)"] +#[ignore = "requires mocking RepositorySet::find_packages with willReturnOnConsecutiveCalls; RepositorySet is a concrete struct with no injectable/overridable find_packages"] fn test_most_stable_version_is_returned_regardless_of_order() { todo!() } #[test] -#[ignore = "not yet ported (VersionSelector over a mocked repository; constraint parsing uses a look-around regex)"] +#[ignore = "requires mocking RepositorySet::find_packages to return fixed package objects; RepositorySet is a concrete struct with no injectable/overridable find_packages"] fn test_highest_version_is_returned() { todo!() } #[test] -#[ignore = "not yet ported (VersionSelector over a mocked repository; constraint parsing uses a look-around regex)"] +#[ignore = "requires mocking RepositorySet::find_packages to return fixed package objects; RepositorySet is a concrete struct with no injectable/overridable find_packages"] fn test_highest_version_matching_stability_is_returned() { todo!() } #[test] -#[ignore = "not yet ported (VersionSelector over a mocked repository; constraint parsing uses a look-around regex)"] +#[ignore = "requires mocking RepositorySet::find_packages to return fixed package objects; RepositorySet is a concrete struct with no injectable/overridable find_packages"] fn test_most_stable_unstable_version_is_returned() { todo!() } #[test] -#[ignore = "not yet ported (VersionSelector over a mocked repository; constraint parsing uses a look-around regex)"] +#[ignore = "requires mocking RepositorySet::find_packages to return fixed package objects; RepositorySet is a concrete struct with no injectable/overridable find_packages"] fn test_default_branch_alias_is_never_returned() { todo!() } #[test] -#[ignore = "not yet ported (VersionSelector over a mocked repository; constraint parsing uses a look-around regex)"] +#[ignore = "requires mocking RepositorySet::find_packages to return an empty list; RepositorySet is a concrete struct with no injectable/overridable find_packages"] fn test_false_returned_on_no_packages() { todo!() } #[test] -#[ignore = "not yet ported (VersionSelector over a mocked repository; constraint parsing uses a look-around regex)"] +#[ignore = "branch-alias cases need Package::set_extra, exposed only on RootPackageHandle, not on the PackageHandle/PackageInterfaceHandle passed to find_recommended_require_version"] fn test_find_recommended_require_version() { todo!() } diff --git a/crates/shirabe/tests/platform/hhvm_detector_test.rs b/crates/shirabe/tests/platform/hhvm_detector_test.rs index 1b35450..12a966a 100644 --- a/crates/shirabe/tests/platform/hhvm_detector_test.rs +++ b/crates/shirabe/tests/platform/hhvm_detector_test.rs @@ -1,6 +1,11 @@ //! ref: composer/tests/Composer/Test/Platform/HhvmDetectorTest.php use shirabe::platform::hhvm_detector::HhvmDetector; +use shirabe::util::Platform; +use shirabe::util::ProcessExecutor; +use shirabe_external_packages::symfony::process::ExecutableFinder; +use shirabe_php_shim::{PhpMixed, defined}; +use shirabe_semver::version_parser::VersionParser; fn set_up() -> HhvmDetector { let hhvm_detector = HhvmDetector::new(None, None); @@ -9,15 +14,55 @@ fn set_up() -> HhvmDetector { } #[test] -#[ignore = "skipped unless running under HHVM (HHVM_VERSION_ID defined), which never holds here"] +#[ignore = "requires HHVM_VERSION_ID constant, which the shim does not define"] fn test_hhvm_version_when_executing_in_hhvm() { let _hhvm_detector = set_up(); todo!() } +#[ignore] #[test] -#[ignore = "needs an installed hhvm executable plus ExecutableFinder/ProcessExecutor; skipped in PHP when hhvm is absent"] fn test_hhvm_version_when_executing_in_php() { - let _hhvm_detector = set_up(); - todo!() + let mut hhvm_detector = set_up(); + if defined("HHVM_VERSION_ID") { + // markTestSkipped('Running with HHVM') + return; + } + if Platform::is_windows() { + // markTestSkipped('Test does not run on Windows') + return; + } + let finder = ExecutableFinder::new(); + let hhvm = finder.find("hhvm", None, &[]); + let hhvm = match hhvm { + Some(hhvm) => hhvm, + None => { + // markTestSkipped('HHVM is not installed') + return; + } + }; + + let detected_version = hhvm_detector.get_version(); + assert!(detected_version.is_some(), "Failed to detect HHVM version"); + let detected_version = detected_version.unwrap(); + + let mut process = ProcessExecutor::new(None); + let mut version = PhpMixed::Null; + let cmd = format!( + "{} --php -d hhvm.jit=0 -r \"echo HHVM_VERSION;\" 2>/dev/null", + ProcessExecutor::escape(&hhvm) + ); + let exit_code = process + .execute(cmd.as_str(), Some(&mut version), ()) + .unwrap(); + assert_eq!(0, exit_code); + + let version = version + .as_string() + .map(|s| s.to_string()) + .unwrap_or_default(); + assert_eq!( + VersionParser.normalize(&version, None).unwrap(), + VersionParser.normalize(&detected_version, None).unwrap() + ); } diff --git a/crates/shirabe/tests/plugin/plugin_installer_test.rs b/crates/shirabe/tests/plugin/plugin_installer_test.rs index 6442b5c..6f67c68 100644 --- a/crates/shirabe/tests/plugin/plugin_installer_test.rs +++ b/crates/shirabe/tests/plugin/plugin_installer_test.rs @@ -23,98 +23,98 @@ impl Drop for TearDown { // The plugin system requires the PHP runtime to load and instantiate plugin classes; the // PluginManager/PluginInstaller is intentionally not implemented yet (TODO(plugin)). +#[ignore = "PluginInstaller and runtime loading of fixture plugin PHP classes (plugin-v1) are not implemented (TODO(plugin))"] #[test] -#[ignore = "the plugin API (PluginManager/PluginInstaller loading PHP plugin classes) is not implemented yet"] fn test_install_new_plugin() { todo!() } +#[ignore = "PluginInstaller and runtime loading of fixture plugin PHP classes are not implemented (TODO(plugin))"] #[test] -#[ignore = "the plugin API (PluginManager/PluginInstaller loading PHP plugin classes) is not implemented yet"] fn test_install_plugin_with_root_package_having_files_autoload() { todo!() } +#[ignore = "PluginInstaller and runtime loading of fixture plugin PHP classes (plugin-v4) are not implemented (TODO(plugin))"] #[test] -#[ignore = "the plugin API (PluginManager/PluginInstaller loading PHP plugin classes) is not implemented yet"] fn test_install_multiple_plugins() { todo!() } +#[ignore = "PluginInstaller.update and runtime plugin class loading/deactivation are not implemented (TODO(plugin))"] #[test] -#[ignore = "the plugin API (PluginManager/PluginInstaller loading PHP plugin classes) is not implemented yet"] fn test_upgrade_with_new_class_name() { todo!() } +#[ignore = "PluginInstaller.uninstall and runtime plugin class loading/uninstall hook are not implemented (TODO(plugin))"] #[test] -#[ignore = "the plugin API (PluginManager/PluginInstaller loading PHP plugin classes) is not implemented yet"] fn test_uninstall() { todo!() } +#[ignore = "PluginInstaller.update and runtime plugin class loading/deactivation are not implemented (TODO(plugin))"] #[test] -#[ignore = "the plugin API (PluginManager/PluginInstaller loading PHP plugin classes) is not implemented yet"] fn test_upgrade_with_same_class_name() { todo!() } +#[ignore = "PluginInstaller and runtime loading of fixture plugin PHP classes are not implemented (TODO(plugin))"] #[test] -#[ignore = "the plugin API (PluginManager/PluginInstaller loading PHP plugin classes) is not implemented yet"] fn test_register_plugin_only_one_time() { todo!() } +#[ignore = "Requires mocking getPluginApiVersion and runtime loading of fixture plugin PHP classes; not implemented (TODO(plugin))"] #[test] -#[ignore = "the plugin API (PluginManager/PluginInstaller loading PHP plugin classes) is not implemented yet"] fn test_star_plugin_version_works_with_any_api_version() { todo!() } +#[ignore = "Requires mocking getPluginApiVersion and runtime loading of fixture plugin PHP classes; not implemented (TODO(plugin))"] #[test] -#[ignore = "the plugin API (PluginManager/PluginInstaller loading PHP plugin classes) is not implemented yet"] fn test_plugin_constraint_works_only_with_certain_api_version() { todo!() } +#[ignore = "Requires mocking getPluginApiVersion and runtime loading of fixture plugin PHP classes; not implemented (TODO(plugin))"] #[test] -#[ignore = "the plugin API (PluginManager/PluginInstaller loading PHP plugin classes) is not implemented yet"] fn test_plugin_range_constraints_work_only_with_certain_api_version() { todo!() } +#[ignore = "get_plugin_capabilities and runtime loading of fixture plugin/capability PHP classes (plugin-v8) are not implemented (TODO(plugin))"] #[test] -#[ignore = "the plugin API (PluginManager/PluginInstaller loading PHP plugin classes) is not implemented yet"] fn test_command_provider_capability() { todo!() } +#[ignore = "Requires a PHP-runtime mock of PluginInterface and get_plugin_capability; not implemented (TODO(plugin))"] #[test] -#[ignore = "the plugin API (PluginManager/PluginInstaller loading PHP plugin classes) is not implemented yet"] fn test_incapable_plugin_is_correctly_detected() { todo!() } +#[ignore = "Requires runtime instantiation of Mock\\Capability via get_plugin_capability; not implemented (TODO(plugin))"] #[test] -#[ignore = "the plugin API (PluginManager/PluginInstaller loading PHP plugin classes) is not implemented yet"] fn test_capability_implements_composer_plugin_api_class_and_is_constructed_with_args() { todo!() } +#[ignore = "Requires runtime get_plugin_capability with a mocked Capable plugin and PHP-class-name capability lookup; not implemented (TODO(plugin))"] #[test] -#[ignore = "the plugin API (PluginManager/PluginInstaller loading PHP plugin classes) is not implemented yet"] fn test_querying_with_invalid_capability_class_name_throws() { todo!() } +#[ignore = "Requires runtime get_plugin_capability with a mocked Capable plugin; not implemented (TODO(plugin))"] #[test] -#[ignore = "the plugin API (PluginManager/PluginInstaller loading PHP plugin classes) is not implemented yet"] fn test_querying_non_provided_capability_returns_null_safely() { todo!() } +#[ignore = "Requires runtime get_plugin_capability with PHP-class-name capability lookup; not implemented (TODO(plugin))"] #[test] -#[ignore = "the plugin API (PluginManager/PluginInstaller loading PHP plugin classes) is not implemented yet"] fn test_querying_with_non_existing_or_wrong_capability_class_types_throws() { todo!() } diff --git a/crates/shirabe/tests/question/strict_confirmation_question_test.rs b/crates/shirabe/tests/question/strict_confirmation_question_test.rs index 9045461..4444289 100644 --- a/crates/shirabe/tests/question/strict_confirmation_question_test.rs +++ b/crates/shirabe/tests/question/strict_confirmation_question_test.rs @@ -5,19 +5,19 @@ // validator are private, so they cannot be exercised directly either. #[test] -#[ignore = "needs Symfony QuestionHelper::ask with ArrayInput/StreamOutput, which are not ported"] +#[ignore = "ArrayInput exposes no public set_stream (its inner Input is pub(crate)), and fopen returns PhpMixed which cannot be bridged to StreamOutput::new's PhpResource"] fn test_ask_confirmation_bad_answer() { todo!() } #[test] -#[ignore = "needs Symfony QuestionHelper::ask with ArrayInput/StreamOutput, which are not ported"] +#[ignore = "ArrayInput exposes no public set_stream (its inner Input is pub(crate)), and fopen returns PhpMixed which cannot be bridged to StreamOutput::new's PhpResource"] fn test_ask_confirmation() { todo!() } #[test] -#[ignore = "needs Symfony QuestionHelper::ask with ArrayInput/StreamOutput, which are not ported"] +#[ignore = "ArrayInput exposes no public set_stream (its inner Input is pub(crate)), and fopen returns PhpMixed which cannot be bridged to StreamOutput::new's PhpResource"] fn test_ask_confirmation_with_custom_true_and_false_answer() { todo!() } diff --git a/crates/shirabe/tests/repository/artifact_repository_test.rs b/crates/shirabe/tests/repository/artifact_repository_test.rs index 5e70650..14475da 100644 --- a/crates/shirabe/tests/repository/artifact_repository_test.rs +++ b/crates/shirabe/tests/repository/artifact_repository_test.rs @@ -13,21 +13,21 @@ fn set_up() { } #[test] -#[ignore = "ArtifactRepository reads archives via ZipArchive/PharData, which are todo!() in the php-shim"] +#[ignore = "ArtifactRepository exposes no public get_packages(); it does not impl RepositoryInterface and initialize/scan_directory are private"] fn test_extracts_configs_from_zip_archives() { set_up(); todo!() } #[test] -#[ignore = "ArtifactRepository reads archives via ZipArchive/PharData, which are todo!() in the php-shim"] +#[ignore = "ArtifactRepository exposes no public get_packages(); it does not impl RepositoryInterface and initialize/scan_directory are private"] fn test_absolute_repo_url_creates_absolute_url_packages() { set_up(); todo!() } #[test] -#[ignore = "ArtifactRepository reads archives via ZipArchive/PharData, which are todo!() in the php-shim"] +#[ignore = "ArtifactRepository exposes no public get_packages(); it does not impl RepositoryInterface and initialize/scan_directory are private"] fn test_relative_repo_url_creates_relative_url_packages() { set_up(); todo!() diff --git a/crates/shirabe/tests/repository/composer_repository_test.rs b/crates/shirabe/tests/repository/composer_repository_test.rs index f600a55..d36c2e1 100644 --- a/crates/shirabe/tests/repository/composer_repository_test.rs +++ b/crates/shirabe/tests/repository/composer_repository_test.rs @@ -3,56 +3,56 @@ // These construct a ComposerRepository with a mocked HttpDownloader/IO/Config and parse // provider/package data whose constraints go through a look-around regex; mocking is not // available and a real HttpDownloader reaches curl_multi_init (todo!()). +#[ignore = "needs PHPUnit getMockBuilder to override loadRootServerFile; no method-mocking framework ported"] #[test] -#[ignore = "mocks HttpDownloader/IO (curl_multi_init todo!()) and parses constraints via a look-around regex"] fn test_load_data() { todo!() } +#[ignore = "needs getMockBuilder to override fetchFile plus ReflectionProperty/ReflectionMethod to set private props and invoke whatProvides; no mocking/reflection ported"] #[test] -#[ignore = "mocks HttpDownloader/IO (curl_multi_init todo!()) and parses constraints via a look-around regex"] fn test_what_provides() { todo!() } +#[ignore = "needs getHttpDownloaderMock test helper (HttpDownloaderMock not ported); real HttpDownloader hits todo!() curl I/O"] #[test] -#[ignore = "mocks HttpDownloader/IO (curl_multi_init todo!()) and parses constraints via a look-around regex"] fn test_search_with_type() { todo!() } +#[ignore = "needs getHttpDownloaderMock test helper (HttpDownloaderMock not ported); real HttpDownloader hits todo!() curl I/O"] #[test] -#[ignore = "mocks HttpDownloader/IO (curl_multi_init todo!()) and parses constraints via a look-around regex"] fn test_search_with_special_chars() { todo!() } +#[ignore = "needs getHttpDownloaderMock test helper (HttpDownloaderMock not ported); real HttpDownloader hits todo!() curl I/O"] #[test] -#[ignore = "mocks HttpDownloader/IO (curl_multi_init todo!()) and parses constraints via a look-around regex"] fn test_search_with_abandoned_packages() { todo!() } +#[ignore = "needs getMockBuilder HttpDownloader mock plus ReflectionObject getMethod/getProperty to set private url and invoke canonicalizeUrl; no mocking/reflection ported"] #[test] -#[ignore = "mocks HttpDownloader/IO (curl_multi_init todo!()) and parses constraints via a look-around regex"] fn test_canonicalize_url() { todo!() } +#[ignore = "needs getHttpDownloaderMock test helper (HttpDownloaderMock not ported); real HttpDownloader hits todo!() curl I/O"] #[test] -#[ignore = "mocks HttpDownloader/IO (curl_multi_init todo!()) and parses constraints via a look-around regex"] fn test_get_provider_names_will_return_partial_package_names() { todo!() } +#[ignore = "needs getHttpDownloaderMock test helper (HttpDownloaderMock not ported); real HttpDownloader hits todo!() curl I/O"] #[test] -#[ignore = "mocks HttpDownloader/IO (curl_multi_init todo!()) and parses constraints via a look-around regex"] fn test_get_security_advisories_assert_repository_http_options_are_used() { todo!() } +#[ignore = "needs getHttpDownloaderMock test helper (HttpDownloaderMock not ported); real HttpDownloader hits todo!() curl I/O"] #[test] -#[ignore = "mocks HttpDownloader/IO (curl_multi_init todo!()) and parses constraints via a look-around regex"] fn test_get_security_advisories_assert_repository_advisories_is_zero_indexed_array_with_consecutive_keys() { todo!() diff --git a/crates/shirabe/tests/repository/filesystem_repository_test.rs b/crates/shirabe/tests/repository/filesystem_repository_test.rs index a14804b..ae6e4ef 100644 --- a/crates/shirabe/tests/repository/filesystem_repository_test.rs +++ b/crates/shirabe/tests/repository/filesystem_repository_test.rs @@ -1,37 +1,161 @@ //! ref: composer/tests/Composer/Test/Repository/FilesystemRepositoryTest.php +use indexmap::IndexMap; +use shirabe::installed_versions::InstalledVersions; +use shirabe::json::json_file::JsonFile; +use shirabe::repository::RepositoryInterface; +use shirabe::repository::filesystem_repository::FilesystemRepository; +use shirabe_php_shim::PhpMixed; + +/// PHP mocks JsonFile::read()/exists(); without a mocking framework the canned read value is +/// materialized as a real temp file whose decoded JSON reproduces the mock return value exactly. +fn create_temp_json_file(contents: &str) -> String { + let mut path = std::env::temp_dir(); + let unique = format!( + "shirabe_filesystemrepositorytest_{}_{}", + std::process::id(), + std::time::SystemTime::now() + .duration_since(std::time::UNIX_EPOCH) + .unwrap() + .as_nanos() + ); + path.push(unique); + std::fs::write(&path, contents.as_bytes()).unwrap(); + path.to_str().unwrap().to_string() +} + +#[ignore] #[test] -#[ignore = "test body not yet ported (todo!() stub)"] fn test_repository_read() { - todo!() + let path = create_temp_json_file( + r#"[{"name": "package1", "version": "1.0.0-beta", "type": "vendor"}]"#, + ); + let json = JsonFile::new(path, None, None).unwrap(); + + let mut repository = FilesystemRepository::new(json, false, None, None).unwrap(); + + let packages = repository.get_packages().unwrap(); + + assert_eq!(packages.len(), 1); + assert_eq!(packages[0].get_name(), "package1"); + assert_eq!(packages[0].get_version(), "1.0.0.0-beta"); + assert_eq!(packages[0].get_type(), "vendor"); } +#[ignore] #[test] -#[ignore = "test body not yet ported (todo!() stub)"] fn test_corrupted_repository_file() { - todo!() + // PHP mocks read() to return the scalar string 'foo'; a real file containing the JSON string + // "foo" decodes to the same value, which the repository rejects as a non-array package list. + let path = create_temp_json_file(r#""foo""#); + let json = JsonFile::new(path, None, None).unwrap(); + + let mut repository = FilesystemRepository::new(json, false, None, None).unwrap(); + + let result = repository.get_packages(); + let err = result.unwrap_err(); + assert!( + err.is::<shirabe::repository::InvalidRepositoryException>(), + "expected InvalidRepositoryException, got: {err}" + ); } +#[ignore] #[test] -#[ignore = "test body not yet ported (todo!() stub)"] fn test_unexistent_repository_file() { - todo!() + let mut path = std::env::temp_dir(); + path.push(format!( + "shirabe_filesystemrepositorytest_missing_{}_{}", + std::process::id(), + std::time::SystemTime::now() + .duration_since(std::time::UNIX_EPOCH) + .unwrap() + .as_nanos() + )); + let json = JsonFile::new(path.to_str().unwrap().to_string(), None, None).unwrap(); + + let mut repository = FilesystemRepository::new(json, false, None, None).unwrap(); + + let packages = repository.get_packages().unwrap(); + assert_eq!(packages.len(), 0); } #[test] -#[ignore = "test body not yet ported (todo!() stub)"] +#[ignore = "requires mocking InstallationManager::get_install_path; write() takes a concrete InstallationManager with no trait/seam to stub the canned per-package paths the PHP test relies on"] fn test_repository_write() { todo!() } #[test] -#[ignore = "test body not yet ported (todo!() stub)"] +#[ignore = "requires mocking InstallationManager::get_install_path (concrete method, no stub seam) plus missing test helpers get_root_package and configure_links"] fn test_repository_writes_installed_php() { todo!() } +#[ignore] #[test] -#[ignore = "test body not yet ported (todo!() stub)"] fn test_safely_load_installed_versions() { - todo!() + let fixtures_dir = format!( + "{}/../../composer/tests/Composer/Test/Repository/Fixtures", + env!("CARGO_MANIFEST_DIR") + ); + let path = format!("{}/installed_complex.php", fixtures_dir); + + let result = FilesystemRepository::safely_load_installed_versions(&path); + assert!(result, "The file should be considered valid"); + + let raw_data = InstalledVersions::get_all_raw_data(); + let raw_data = raw_data.last().cloned().unwrap(); + + let mut root: IndexMap<String, PhpMixed> = IndexMap::new(); + root.insert( + "install_path".to_string(), + PhpMixed::String(format!("{}/./", fixtures_dir)), + ); + root.insert( + "aliases".to_string(), + PhpMixed::List(vec![ + PhpMixed::String("1.10.x-dev".to_string()), + PhpMixed::String("2.10.x-dev".to_string()), + ]), + ); + root.insert("name".to_string(), PhpMixed::String("__root__".to_string())); + root.insert("true".to_string(), PhpMixed::Bool(true)); + root.insert("false".to_string(), PhpMixed::Bool(false)); + root.insert("null".to_string(), PhpMixed::Null); + + let mut a_provider: IndexMap<String, PhpMixed> = IndexMap::new(); + a_provider.insert( + "foo".to_string(), + PhpMixed::String("simple string/no backslash".to_string()), + ); + a_provider.insert( + "install_path".to_string(), + PhpMixed::String(format!( + "{}/vendor/{{${{passthru('bash -i')}}}}", + fixtures_dir + )), + ); + a_provider.insert("empty array".to_string(), PhpMixed::List(vec![])); + + let mut c_c: IndexMap<String, PhpMixed> = IndexMap::new(); + c_c.insert( + "install_path".to_string(), + PhpMixed::String("/foo/bar/ven/do{}r/c/c${}".to_string()), + ); + c_c.insert("aliases".to_string(), PhpMixed::List(vec![])); + c_c.insert( + "reference".to_string(), + PhpMixed::String("{${passthru('bash -i')}} Foo\\Bar\n\ttab\u{0b}verticaltab\0".to_string()), + ); + + let mut versions: IndexMap<String, PhpMixed> = IndexMap::new(); + versions.insert("a/provider".to_string(), PhpMixed::Array(a_provider)); + versions.insert("c/c".to_string(), PhpMixed::Array(c_c)); + + let mut expected: IndexMap<String, PhpMixed> = IndexMap::new(); + expected.insert("root".to_string(), PhpMixed::Array(root)); + expected.insert("versions".to_string(), PhpMixed::Array(versions)); + + assert_eq!(raw_data, expected); } diff --git a/crates/shirabe/tests/repository/path_repository_test.rs b/crates/shirabe/tests/repository/path_repository_test.rs index c2e8e72..c2cf892 100644 --- a/crates/shirabe/tests/repository/path_repository_test.rs +++ b/crates/shirabe/tests/repository/path_repository_test.rs @@ -5,50 +5,50 @@ // ported, so there is no way to drive these tests. The unversioned cases additionally // require the VersionGuesser (git). +#[ignore = "PathRepository exposes no public RepositoryInterface::get_packages/count/has_package; the inner ArrayRepository delegation is not ported"] #[test] -#[ignore = "PathRepository exposes no public getPackages/count/hasPackage (RepositoryInterface delegation not ported)"] fn test_load_package_from_file_system_with_incorrect_path() { todo!() } +#[ignore = "PathRepository exposes no public RepositoryInterface::get_packages/count/has_package; the inner ArrayRepository delegation is not ported"] #[test] -#[ignore = "PathRepository exposes no public getPackages/count/hasPackage (RepositoryInterface delegation not ported)"] fn test_load_package_from_file_system_with_version() { todo!() } +#[ignore = "PathRepository exposes no public RepositoryInterface::get_packages/count/has_package; the inner ArrayRepository delegation is not ported"] #[test] -#[ignore = "PathRepository exposes no public getPackages/count/hasPackage (RepositoryInterface delegation not ported)"] fn test_load_package_from_file_system_without_version() { todo!() } +#[ignore = "PathRepository exposes no public RepositoryInterface::get_packages/count/has_package; the inner ArrayRepository delegation is not ported"] #[test] -#[ignore = "PathRepository exposes no public getPackages/count/hasPackage (RepositoryInterface delegation not ported)"] fn test_load_package_from_file_system_with_wildcard() { todo!() } +#[ignore = "PathRepository exposes no public RepositoryInterface::get_packages/count/has_package; the inner ArrayRepository delegation is not ported"] #[test] -#[ignore = "PathRepository exposes no public getPackages/count/hasPackage (RepositoryInterface delegation not ported)"] fn test_load_package_with_explicit_versions() { todo!() } +#[ignore = "PathRepository exposes no public RepositoryInterface::get_packages/count/has_package; the inner ArrayRepository delegation is not ported"] #[test] -#[ignore = "PathRepository exposes no public getPackages/count/hasPackage (RepositoryInterface delegation not ported)"] fn test_url_remains_relative() { todo!() } +#[ignore = "PathRepository exposes no public RepositoryInterface::get_packages/count/has_package; the inner ArrayRepository delegation is not ported"] #[test] -#[ignore = "PathRepository exposes no public getPackages/count/hasPackage (RepositoryInterface delegation not ported)"] fn test_reference_none() { todo!() } +#[ignore = "PathRepository exposes no public RepositoryInterface::get_packages/count/has_package; the inner ArrayRepository delegation is not ported"] #[test] -#[ignore = "PathRepository exposes no public getPackages/count/hasPackage (RepositoryInterface delegation not ported)"] fn test_reference_config() { todo!() } diff --git a/crates/shirabe/tests/repository/platform_repository_test.rs b/crates/shirabe/tests/repository/platform_repository_test.rs index d19b615..fb84fd6 100644 --- a/crates/shirabe/tests/repository/platform_repository_test.rs +++ b/crates/shirabe/tests/repository/platform_repository_test.rs @@ -1,40 +1,71 @@ //! ref: composer/tests/Composer/Test/Repository/PlatformRepositoryTest.php +use shirabe::repository::PlatformRepository; + // These probe runtime/extension/library versions and assert the synthesized platform // packages; the detection mocks ProcessExecutor/Runtime/HhvmDetector and the package // versions are parsed through a look-around regex. #[test] -#[ignore = "not yet ported (platform detection mocks Runtime/ProcessExecutor; version parsing uses a look-around regex)"] +#[ignore = "requires PHPUnit getMockBuilder mock of Composer\\Platform\\HhvmDetector (willReturn getVersion); Runtime/HhvmDetector are concrete structs with no mock/override mechanism"] fn test_hhvm_package() { todo!() } #[test] -#[ignore = "not yet ported (platform detection mocks Runtime/ProcessExecutor; version parsing uses a look-around regex)"] +#[ignore = "requires PHPUnit getMockBuilder mock of Composer\\Platform\\Runtime (willReturnCallback/willReturnMap for hasConstant/getConstant/invoke/getExtensions); no mocking framework and Runtime is a concrete struct"] fn test_php_version() { todo!() } #[test] -#[ignore = "not yet ported (platform detection mocks Runtime/ProcessExecutor; version parsing uses a look-around regex)"] +#[ignore = "requires PHPUnit getMockBuilder mock of Composer\\Platform\\Runtime (expects(once)/with/willReturn for invoke, willReturnCallback for getConstant); no mocking framework"] fn test_inet_pton_regression() { todo!() } #[test] -#[ignore = "not yet ported (platform detection mocks Runtime/ProcessExecutor; version parsing uses a look-around regex)"] +#[ignore = "requires PHPUnit getMockBuilder mock of Composer\\Platform\\Runtime (willReturnMap/willReturnCallback for getExtensions/getExtensionVersion/getExtensionInfo/invoke/hasConstant/getConstant/hasClass/construct) plus ResourceBundleStub/ImagickStub; no mocking framework"] fn test_library_information() { todo!() } #[test] -#[ignore = "not yet ported (platform detection mocks Runtime/ProcessExecutor; version parsing uses a look-around regex)"] +#[ignore = "requires PHPUnit getMockBuilder mock of Composer\\Platform\\Runtime (willReturnMap for getConstant/getExtensions); no mocking framework and Runtime is a concrete struct"] fn test_composer_platform_version() { todo!() } #[test] -#[ignore = "not yet ported (platform detection mocks Runtime/ProcessExecutor; version parsing uses a look-around regex)"] +#[ignore] fn test_valid_platform_packages() { - todo!() + let cases: Vec<(&str, bool)> = vec![ + ("php", true), + ("php-debug", true), + ("php-ipv6", true), + ("php-64bit", true), + ("php-zts", true), + ("hhvm", true), + ("hhvm-foo", false), + ("ext-foo", true), + ("ext-123", true), + ("extfoo", false), + ("ext", false), + ("lib-foo", true), + ("lib-123", true), + ("libfoo", false), + ("lib", false), + ("composer", true), + ("composer-foo", false), + ("composer-plugin-api", true), + ("composer-plugin", false), + ("composer-runtime-api", true), + ("composer-runtime", false), + ]; + + for (package_name, expectation) in cases { + assert_eq!( + expectation, + PlatformRepository::is_platform_package(package_name) + ); + } } diff --git a/crates/shirabe/tests/repository/repository_factory_test.rs b/crates/shirabe/tests/repository/repository_factory_test.rs index 98e2c85..a0eb81f 100644 --- a/crates/shirabe/tests/repository/repository_factory_test.rs +++ b/crates/shirabe/tests/repository/repository_factory_test.rs @@ -5,7 +5,7 @@ use shirabe::repository::RepositoryFactory; use shirabe_php_shim::PhpMixed; #[test] -#[ignore = "mocks IO/Config/HttpDownloader/EventDispatcher and reads the private repositoryClasses via reflection; not portable"] +#[ignore = "PHP test uses ReflectionProperty to read the private RepositoryManager::repository_classes field; no public accessor for repository_classes keys exists in the Rust impl"] fn test_manager_with_all_repository_types() { todo!() } diff --git a/crates/shirabe/tests/repository/repository_manager_test.rs b/crates/shirabe/tests/repository/repository_manager_test.rs index dec018a..bb85d6a 100644 --- a/crates/shirabe/tests/repository/repository_manager_test.rs +++ b/crates/shirabe/tests/repository/repository_manager_test.rs @@ -4,7 +4,17 @@ // curl_multi_init, todo!()) with a mocked IO/Config/EventDispatcher and exercise repo // creation/prepending/wrapping. +use std::cell::RefCell; +use std::rc::Rc; + +use indexmap::IndexMap; +use shirabe::config::Config; +use shirabe::io::IOInterface; +use shirabe::io::null_io::NullIO; +use shirabe::repository::{ArrayRepository, RepositoryInterfaceHandle, RepositoryManager}; use shirabe::util::filesystem::Filesystem; +use shirabe::util::http_downloader::HttpDownloader; +use shirabe_php_shim::PhpMixed; use tempfile::TempDir; struct SetUp { @@ -39,32 +49,171 @@ impl Drop for TearDown { } } +fn null_io() -> Rc<RefCell<dyn IOInterface>> { + Rc::new(RefCell::new(NullIO::new())) +} + +fn http_downloader(io: &Rc<RefCell<dyn IOInterface>>) -> Rc<RefCell<HttpDownloader>> { + let config = Rc::new(RefCell::new(Config::new(false, None))); + Rc::new(RefCell::new(HttpDownloader::new( + io.clone(), + config, + IndexMap::new(), + true, + ))) +} + +fn str_config(pairs: &[(&str, PhpMixed)]) -> IndexMap<String, PhpMixed> { + let mut c: IndexMap<String, PhpMixed> = IndexMap::new(); + for (k, v) in pairs { + c.insert(k.to_string(), v.clone()); + } + c +} + #[test] -#[ignore = "RepositoryManager::new builds an HttpDownloader (curl_multi_init todo!()) and mocks IO/Config"] +#[ignore] fn test_prepend() { let SetUp { tmpdir } = set_up(); let _tear_down = TearDown::new(tmpdir.path().to_path_buf()); - todo!() + + let io = null_io(); + let config = Rc::new(RefCell::new(Config::new(false, None))); + let mut rm = RepositoryManager::new(io.clone(), config, http_downloader(&io), None, None); + + let repository1 = RepositoryInterfaceHandle::new(ArrayRepository::new(vec![]).unwrap()); + let repository2 = RepositoryInterfaceHandle::new(ArrayRepository::new(vec![]).unwrap()); + rm.add_repository(repository1.clone()); + rm.prepend_repository(repository2.clone()); + + assert_eq!(&vec![repository2, repository1], rm.get_repositories()); } #[test] -#[ignore = "RepositoryManager::new builds an HttpDownloader (curl_multi_init todo!()) and mocks IO/Config"] +#[ignore] fn test_repo_creation() { let SetUp { tmpdir } = set_up(); let _tear_down = TearDown::new(tmpdir.path().to_path_buf()); - todo!() + + let io = null_io(); + let config = Rc::new(RefCell::new(Config::new(false, None))); + let mut rm = + RepositoryManager::new(io.clone(), config.clone(), http_downloader(&io), None, None); + + config.borrow_mut().merge( + &str_config(&[( + "config", + PhpMixed::Array(str_config(&[( + "cache-repo-dir", + PhpMixed::String(tmpdir.path().to_string_lossy().to_string()), + )])), + )]), + "unknown", + ); + + rm.set_repository_class("composer", "Composer\\Repository\\ComposerRepository"); + rm.set_repository_class("vcs", "Composer\\Repository\\VcsRepository"); + rm.set_repository_class("package", "Composer\\Repository\\PackageRepository"); + rm.set_repository_class("pear", "Composer\\Repository\\PearRepository"); + rm.set_repository_class("git", "Composer\\Repository\\VcsRepository"); + rm.set_repository_class("svn", "Composer\\Repository\\VcsRepository"); + rm.set_repository_class("perforce", "Composer\\Repository\\VcsRepository"); + rm.set_repository_class("hg", "Composer\\Repository\\VcsRepository"); + rm.set_repository_class("artifact", "Composer\\Repository\\ArtifactRepository"); + + let cases: Vec<(&str, IndexMap<String, PhpMixed>)> = vec![ + ( + "composer", + str_config(&[("url", PhpMixed::String("http://example.org".to_string()))]), + ), + ( + "vcs", + str_config(&[( + "url", + PhpMixed::String("http://github.com/foo/bar".to_string()), + )]), + ), + ( + "git", + str_config(&[( + "url", + PhpMixed::String("http://github.com/foo/bar".to_string()), + )]), + ), + ( + "git", + str_config(&[( + "url", + PhpMixed::String("git@example.org:foo/bar.git".to_string()), + )]), + ), + ( + "svn", + str_config(&[( + "url", + PhpMixed::String("svn://example.org/foo/bar".to_string()), + )]), + ), + ( + "package", + str_config(&[("package", PhpMixed::Array(IndexMap::new()))]), + ), + ( + "artifact", + str_config(&[("url", PhpMixed::String("/path/to/zips".to_string()))]), + ), + ]; + + for (r#type, options) in cases { + rm.create_repository( + "composer", + str_config(&[("url", PhpMixed::String("http://example.org".to_string()))]), + None, + ) + .unwrap(); + rm.create_repository(r#type, options, None).unwrap(); + } } #[test] -#[ignore = "RepositoryManager::new builds an HttpDownloader (curl_multi_init todo!()) and mocks IO/Config"] +#[ignore] fn test_invalid_repo_creation_throws() { let SetUp { tmpdir } = set_up(); let _tear_down = TearDown::new(tmpdir.path().to_path_buf()); - todo!() + + let io = null_io(); + let config = Rc::new(RefCell::new(Config::new(false, None))); + let rm = RepositoryManager::new(io.clone(), config.clone(), http_downloader(&io), None, None); + + config.borrow_mut().merge( + &str_config(&[( + "config", + PhpMixed::Array(str_config(&[( + "cache-repo-dir", + PhpMixed::String(tmpdir.path().to_string_lossy().to_string()), + )])), + )]), + "unknown", + ); + + let cases: Vec<(&str, IndexMap<String, PhpMixed>)> = vec![ + ( + "pear", + str_config(&[( + "url", + PhpMixed::String("http://pear.example.org/foo".to_string()), + )]), + ), + ("invalid", IndexMap::new()), + ]; + + for (r#type, options) in cases { + assert!(rm.create_repository(r#type, options, None).is_err()); + } } #[test] -#[ignore = "RepositoryManager::new builds an HttpDownloader (curl_multi_init todo!()) and mocks IO/Config"] +#[ignore = "PathRepository does not implement RepositoryInterface (only ConfigurableRepositoryInterface), so it cannot live inside a RepositoryInterfaceHandle nor be recovered via as_any().downcast_ref::<PathRepository>(); the assertInstanceOf(PathRepository) check is unportable"] fn test_filter_repo_wrapping() { let SetUp { tmpdir } = set_up(); let _tear_down = TearDown::new(tmpdir.path().to_path_buf()); diff --git a/crates/shirabe/tests/repository/vcs/forgejo_driver_test.rs b/crates/shirabe/tests/repository/vcs/forgejo_driver_test.rs index a67bce3..0836fc5 100644 --- a/crates/shirabe/tests/repository/vcs/forgejo_driver_test.rs +++ b/crates/shirabe/tests/repository/vcs/forgejo_driver_test.rs @@ -93,7 +93,7 @@ fn test_supports() { // Forgejo API responses; mocking is not available, and a real HttpDownloader reaches // curl_multi_init (todo!()). #[test] -#[ignore = "constructs a ForgejoDriver and mocks the HttpDownloader (curl_multi_init todo!())"] +#[ignore = "HttpDownloaderMock (getHttpDownloaderMock) and the IOInterface MockObject are not ported"] fn test_public_repository() { let SetUp { home, @@ -107,7 +107,7 @@ fn test_public_repository() { } #[test] -#[ignore = "constructs a ForgejoDriver and mocks the HttpDownloader (curl_multi_init todo!())"] +#[ignore = "HttpDownloaderMock (getHttpDownloaderMock) and the IOInterface MockObject are not ported"] fn test_get_branches() { let SetUp { home, @@ -121,7 +121,7 @@ fn test_get_branches() { } #[test] -#[ignore = "constructs a ForgejoDriver and mocks the HttpDownloader (curl_multi_init todo!())"] +#[ignore = "HttpDownloaderMock (getHttpDownloaderMock) and the IOInterface MockObject are not ported"] fn test_get_tags() { let SetUp { home, @@ -135,7 +135,7 @@ fn test_get_tags() { } #[test] -#[ignore = "constructs a ForgejoDriver and mocks the HttpDownloader (curl_multi_init todo!())"] +#[ignore = "HttpDownloaderMock (getHttpDownloaderMock) and the IOInterface MockObject are not ported"] fn test_get_empty_file_content() { let SetUp { home, diff --git a/crates/shirabe/tests/repository/vcs/git_bitbucket_driver_test.rs b/crates/shirabe/tests/repository/vcs/git_bitbucket_driver_test.rs index b6759b5..2fedba1 100644 --- a/crates/shirabe/tests/repository/vcs/git_bitbucket_driver_test.rs +++ b/crates/shirabe/tests/repository/vcs/git_bitbucket_driver_test.rs @@ -103,7 +103,7 @@ fn test_supports() { // Bitbucket API responses; mocking is not available, and a real HttpDownloader reaches // curl_multi_init (todo!()). #[test] -#[ignore = "constructs a GitBitbucketDriver and mocks the HttpDownloader (curl_multi_init todo!())"] +#[ignore = "needs IOInterface mock (getMockBuilder) and getHttpDownloaderMock/HttpDownloaderMock (not ported); set_up()'s io and http_downloader are todo!() and real HttpDownloader hits todo!() curl I/O"] fn test_get_root_identifier_wrong_scm_type() { let SetUp { home, @@ -116,7 +116,7 @@ fn test_get_root_identifier_wrong_scm_type() { } #[test] -#[ignore = "constructs a GitBitbucketDriver and mocks the HttpDownloader (curl_multi_init todo!())"] +#[ignore = "needs IOInterface mock (getMockBuilder) and getHttpDownloaderMock/HttpDownloaderMock (not ported); set_up()'s io and http_downloader are todo!() and real HttpDownloader hits todo!() curl I/O"] fn test_driver() { let SetUp { home, @@ -129,7 +129,7 @@ fn test_driver() { } #[test] -#[ignore = "constructs a GitBitbucketDriver and mocks the HttpDownloader (curl_multi_init todo!())"] +#[ignore = "needs IOInterface mock (getMockBuilder) and getHttpDownloaderMock/HttpDownloaderMock (not ported); depends on test_driver's driver and set_up()'s todo!() mocks"] fn test_get_params() { let SetUp { home, @@ -142,7 +142,7 @@ fn test_get_params() { } #[test] -#[ignore = "constructs a GitBitbucketDriver and mocks the HttpDownloader (curl_multi_init todo!())"] +#[ignore = "needs IOInterface mock (getMockBuilder) and getHttpDownloaderMock/HttpDownloaderMock (not ported); set_up()'s io and http_downloader are todo!()"] fn test_initialize_invalid_repository_url() { let SetUp { home, @@ -155,7 +155,7 @@ fn test_initialize_invalid_repository_url() { } #[test] -#[ignore = "constructs a GitBitbucketDriver and mocks the HttpDownloader (curl_multi_init todo!())"] +#[ignore = "needs IOInterface mock (getMockBuilder) and getHttpDownloaderMock/HttpDownloaderMock (not ported); set_up()'s io and http_downloader are todo!() and real HttpDownloader hits todo!() curl I/O"] fn test_invalid_support_data() { let SetUp { home, diff --git a/crates/shirabe/tests/repository/vcs/git_driver_test.rs b/crates/shirabe/tests/repository/vcs/git_driver_test.rs index 1aacbf9..56c2755 100644 --- a/crates/shirabe/tests/repository/vcs/git_driver_test.rs +++ b/crates/shirabe/tests/repository/vcs/git_driver_test.rs @@ -64,7 +64,7 @@ impl Drop for TearDown { } #[test] -#[ignore = "constructs a GitDriver and mocks a ProcessExecutor/HttpDownloader (curl_multi_init todo!())"] +#[ignore = "requires ProcessExecutor mock (getProcessExecutorMock/expects) and Reflection setRepoDir, neither available"] fn test_get_root_identifier_from_remote_local_repository() { let SetUp { home, @@ -76,7 +76,7 @@ fn test_get_root_identifier_from_remote_local_repository() { } #[test] -#[ignore = "constructs a GitDriver and mocks a ProcessExecutor/HttpDownloader (curl_multi_init todo!())"] +#[ignore = "requires ProcessExecutor mock (getProcessExecutorMock/expects), IO mock and Reflection setRepoDir, none available"] fn test_get_root_identifier_from_remote() { let SetUp { home, @@ -88,7 +88,7 @@ fn test_get_root_identifier_from_remote() { } #[test] -#[ignore = "constructs a GitDriver and mocks a ProcessExecutor/HttpDownloader (curl_multi_init todo!())"] +#[ignore = "requires ProcessExecutor mock (getProcessExecutorMock/expects) and Reflection setRepoDir, neither available"] fn test_get_root_identifier_from_local_with_network_disabled() { let SetUp { home, @@ -100,7 +100,7 @@ fn test_get_root_identifier_from_local_with_network_disabled() { } #[test] -#[ignore = "constructs a GitDriver and mocks a ProcessExecutor/HttpDownloader (curl_multi_init todo!())"] +#[ignore = "requires ProcessExecutor mock (getProcessExecutorMock/expects), IOInterface mock and Reflection setRepoDir, none available"] fn test_get_branches_filter_invalid_branch_names() { let SetUp { home, @@ -112,7 +112,7 @@ fn test_get_branches_filter_invalid_branch_names() { } #[test] -#[ignore = "constructs a GitDriver and mocks a ProcessExecutor/HttpDownloader (curl_multi_init todo!())"] +#[ignore = "requires ProcessExecutor mock (getProcessExecutorMock) and IOInterface/HttpDownloader mocks, none available"] fn test_file_get_content_invalid_identifier() { let SetUp { home, @@ -124,7 +124,7 @@ fn test_file_get_content_invalid_identifier() { } #[test] -#[ignore = "constructs a GitDriver and mocks a ProcessExecutor/HttpDownloader (curl_multi_init todo!())"] +#[ignore = "requires ProcessExecutor mock (getProcessExecutorMock) and IOInterface/HttpDownloader mocks, none available"] fn test_get_change_date_invalid_identifier() { let SetUp { home, diff --git a/crates/shirabe/tests/repository/vcs/github_driver_test.rs b/crates/shirabe/tests/repository/vcs/github_driver_test.rs index 3e97599..5313c0b 100644 --- a/crates/shirabe/tests/repository/vcs/github_driver_test.rs +++ b/crates/shirabe/tests/repository/vcs/github_driver_test.rs @@ -84,7 +84,7 @@ fn test_supports() { // GitHub API responses; mocking is not available, and a real HttpDownloader reaches // curl_multi_init (todo!()). #[test] -#[ignore = "constructs a GitHubDriver and mocks the HttpDownloader/IO (curl_multi_init todo!())"] +#[ignore = "HttpDownloaderMock (getHttpDownloaderMock), ProcessExecutorMock, the IOInterface MockObject (askAndHideAnswer/setAuthentication), and setAttribute reflection are not ported"] fn test_private_repository() { let SetUp { home, config: _ } = set_up(); let _tear_down = TearDown::new(home.path().to_path_buf()); @@ -92,7 +92,7 @@ fn test_private_repository() { } #[test] -#[ignore = "constructs a GitHubDriver and mocks the HttpDownloader/IO (curl_multi_init todo!())"] +#[ignore = "HttpDownloaderMock (getHttpDownloaderMock), ProcessExecutorMock, the IOInterface MockObject, and setAttribute reflection are not ported"] fn test_public_repository() { let SetUp { home, config: _ } = set_up(); let _tear_down = TearDown::new(home.path().to_path_buf()); @@ -100,7 +100,7 @@ fn test_public_repository() { } #[test] -#[ignore = "constructs a GitHubDriver and mocks the HttpDownloader/IO (curl_multi_init todo!())"] +#[ignore = "HttpDownloaderMock (getHttpDownloaderMock), ProcessExecutorMock, the IOInterface MockObject, and setAttribute reflection are not ported"] fn test_public_repository2() { let SetUp { home, config: _ } = set_up(); let _tear_down = TearDown::new(home.path().to_path_buf()); @@ -108,7 +108,7 @@ fn test_public_repository2() { } #[test] -#[ignore = "constructs a GitHubDriver and mocks the HttpDownloader/IO (curl_multi_init todo!())"] +#[ignore = "HttpDownloaderMock (getHttpDownloaderMock), ProcessExecutorMock, the IOInterface MockObject, and setAttribute reflection are not ported"] fn test_invalid_support_data() { let SetUp { home, config: _ } = set_up(); let _tear_down = TearDown::new(home.path().to_path_buf()); @@ -116,7 +116,7 @@ fn test_invalid_support_data() { } #[test] -#[ignore = "constructs a GitHubDriver and mocks the HttpDownloader/IO (curl_multi_init todo!())"] +#[ignore = "HttpDownloaderMock (getHttpDownloaderMock), ProcessExecutorMock, the IOInterface MockObject, setAttribute reflection, and the fundingUrlProvider data provider are not ported"] fn test_funding_format() { let SetUp { home, config: _ } = set_up(); let _tear_down = TearDown::new(home.path().to_path_buf()); @@ -124,7 +124,7 @@ fn test_funding_format() { } #[test] -#[ignore = "constructs a GitHubDriver and mocks the HttpDownloader/IO (curl_multi_init todo!())"] +#[ignore = "HttpDownloaderMock (getHttpDownloaderMock), ProcessExecutorMock, the IOInterface MockObject, and setAttribute reflection are not ported"] fn test_public_repository_archived() { let SetUp { home, config: _ } = set_up(); let _tear_down = TearDown::new(home.path().to_path_buf()); @@ -132,7 +132,7 @@ fn test_public_repository_archived() { } #[test] -#[ignore = "constructs a GitHubDriver and mocks the HttpDownloader/IO (curl_multi_init todo!())"] +#[ignore = "HttpDownloaderMock (getHttpDownloaderMock), ProcessExecutorMock, and the IOInterface MockObject are not ported"] fn test_private_repository_no_interaction() { let SetUp { home, config: _ } = set_up(); let _tear_down = TearDown::new(home.path().to_path_buf()); @@ -140,7 +140,7 @@ fn test_private_repository_no_interaction() { } #[test] -#[ignore = "constructs a GitHubDriver and mocks the HttpDownloader/IO (curl_multi_init todo!())"] +#[ignore = "IOInterface MockObject (getMockBuilder) and HttpDownloader MockObject (getMockBuilder) plus the invalidUrlProvider data provider are not ported"] fn test_initialize_invalid_repo_url() { let SetUp { home, config: _ } = set_up(); let _tear_down = TearDown::new(home.path().to_path_buf()); @@ -148,7 +148,7 @@ fn test_initialize_invalid_repo_url() { } #[test] -#[ignore = "constructs a GitHubDriver and mocks the HttpDownloader/IO (curl_multi_init todo!())"] +#[ignore = "HttpDownloaderMock (getHttpDownloaderMock), ProcessExecutorMock, and the IOInterface MockObject are not ported"] fn test_get_empty_file_content() { let SetUp { home, config: _ } = set_up(); let _tear_down = TearDown::new(home.path().to_path_buf()); diff --git a/crates/shirabe/tests/repository/vcs/gitlab_driver_test.rs b/crates/shirabe/tests/repository/vcs/gitlab_driver_test.rs index 1e13ada..0333a2b 100644 --- a/crates/shirabe/tests/repository/vcs/gitlab_driver_test.rs +++ b/crates/shirabe/tests/repository/vcs/gitlab_driver_test.rs @@ -1,107 +1,177 @@ //! ref: composer/tests/Composer/Test/Repository/Vcs/GitLabDriverTest.php -// All cases either mock the HttpDownloader/IO to return GitLab API responses (a real -// HttpDownloader reaches curl_multi_init, todo!()), or — for testSupports — rely on the -// gitlab-domains configured in setUp plus the openssl extension, which are not modeled. +use std::cell::RefCell; +use std::rc::Rc; + +use indexmap::IndexMap; +use shirabe::config::Config; +use shirabe::io::IOInterface; +use shirabe::io::null_io::NullIO; +use shirabe::repository::vcs::GitLabDriver; +use shirabe_php_shim::{PhpMixed, extension_loaded}; + +// Mirrors GitLabDriverTest::setUp's `gitlab-domains` configuration. +fn make_config() -> Config { + let mut config = Config::new(true, None); + let mut top: IndexMap<String, PhpMixed> = IndexMap::new(); + let mut config_section: IndexMap<String, PhpMixed> = IndexMap::new(); + config_section.insert( + "gitlab-domains".to_string(), + PhpMixed::List(vec![ + PhpMixed::String("mycompany.com/gitlab".to_string()), + PhpMixed::String("gitlab.mycompany.com".to_string()), + PhpMixed::String("othercompany.com/nested/gitlab".to_string()), + PhpMixed::String("gitlab.com".to_string()), + PhpMixed::String("gitlab.mycompany.local".to_string()), + ]), + ); + top.insert("config".to_string(), PhpMixed::Array(config_section)); + config.merge(&top, Config::SOURCE_UNKNOWN); + config +} #[test] -#[ignore = "GitLabDriver tests mock HttpDownloader/IO (curl_multi_init todo!()) or need setUp gitlab-domains config"] +#[ignore = "HttpDownloaderMock (getHttpDownloaderMock) and the IOInterface MockObject are not ported"] fn test_initialize() { todo!() } #[test] -#[ignore = "GitLabDriver tests mock HttpDownloader/IO (curl_multi_init todo!()) or need setUp gitlab-domains config"] +#[ignore = "HttpDownloaderMock (getHttpDownloaderMock) and the IOInterface MockObject are not ported"] fn test_initialize_public_project() { todo!() } #[test] -#[ignore = "GitLabDriver tests mock HttpDownloader/IO (curl_multi_init todo!()) or need setUp gitlab-domains config"] +#[ignore = "HttpDownloaderMock (getHttpDownloaderMock) and the IOInterface MockObject are not ported"] fn test_initialize_public_project_as_anonymous() { todo!() } #[test] -#[ignore = "GitLabDriver tests mock HttpDownloader/IO (curl_multi_init todo!()) or need setUp gitlab-domains config"] +#[ignore = "HttpDownloaderMock (getHttpDownloaderMock) and the IOInterface MockObject are not ported"] fn test_initialize_with_port_number() { todo!() } #[test] -#[ignore = "GitLabDriver tests mock HttpDownloader/IO (curl_multi_init todo!()) or need setUp gitlab-domains config"] +#[ignore = "depends on testInitialize, which needs HttpDownloaderMock + setAttribute (\\ReflectionProperty) not ported"] fn test_invalid_support_data() { todo!() } #[test] -#[ignore = "GitLabDriver tests mock HttpDownloader/IO (curl_multi_init todo!()) or need setUp gitlab-domains config"] +#[ignore = "depends on testInitialize, which needs HttpDownloaderMock + the IOInterface MockObject not ported"] fn test_get_dist() { todo!() } #[test] -#[ignore = "GitLabDriver tests mock HttpDownloader/IO (curl_multi_init todo!()) or need setUp gitlab-domains config"] +#[ignore = "depends on testInitialize, which needs HttpDownloaderMock + the IOInterface MockObject not ported"] fn test_get_source() { todo!() } #[test] -#[ignore = "GitLabDriver tests mock HttpDownloader/IO (curl_multi_init todo!()) or need setUp gitlab-domains config"] +#[ignore = "depends on testInitializePublicProject, which needs HttpDownloaderMock + the IOInterface MockObject not ported"] fn test_get_source_given_public_project() { todo!() } #[test] -#[ignore = "GitLabDriver tests mock HttpDownloader/IO (curl_multi_init todo!()) or need setUp gitlab-domains config"] +#[ignore = "depends on testInitialize, which needs HttpDownloaderMock + the IOInterface MockObject not ported"] fn test_get_tags() { todo!() } #[test] -#[ignore = "GitLabDriver tests mock HttpDownloader/IO (curl_multi_init todo!()) or need setUp gitlab-domains config"] +#[ignore = "depends on testInitialize, which needs HttpDownloaderMock + the IOInterface MockObject not ported"] fn test_get_paginated_refs() { todo!() } #[test] -#[ignore = "GitLabDriver tests mock HttpDownloader/IO (curl_multi_init todo!()) or need setUp gitlab-domains config"] +#[ignore = "depends on testInitialize, which needs HttpDownloaderMock + the IOInterface MockObject not ported"] fn test_get_branches() { todo!() } #[test] -#[ignore = "GitLabDriver tests mock HttpDownloader/IO (curl_multi_init todo!()) or need setUp gitlab-domains config"] +#[ignore] fn test_supports() { - todo!() + for (url, expected) in data_for_test_supports() { + let io: Rc<RefCell<dyn IOInterface>> = Rc::new(RefCell::new(NullIO::new())); + let config = Rc::new(RefCell::new(make_config())); + + assert_eq!( + expected, + GitLabDriver::supports(io, config, url, false).unwrap() + ); + } +} + +fn data_for_test_supports() -> Vec<(&'static str, bool)> { + let openssl = extension_loaded("openssl"); + vec![ + ("http://gitlab.com/foo/bar", true), + ("http://gitlab.mycompany.com:5443/foo/bar", true), + ("http://gitlab.com/foo/bar/", true), + ("http://gitlab.com/foo/bar/", true), + ("http://gitlab.com/foo/bar.git", true), + ("http://gitlab.com/foo/bar.git", true), + ("http://gitlab.com/foo/bar.baz.git", true), + ("https://gitlab.com/foo/bar", openssl), + ("https://gitlab.mycompany.com:5443/foo/bar", openssl), + ("git@gitlab.com:foo/bar.git", openssl), + ("git@example.com:foo/bar.git", false), + ("http://example.com/foo/bar", false), + ("http://mycompany.com/gitlab/mygroup/myproject", true), + ("https://mycompany.com/gitlab/mygroup/myproject", openssl), + ( + "http://othercompany.com/nested/gitlab/mygroup/myproject", + true, + ), + ( + "https://othercompany.com/nested/gitlab/mygroup/myproject", + openssl, + ), + ( + "http://gitlab.com/mygroup/mysubgroup/mysubsubgroup/myproject", + true, + ), + ( + "https://gitlab.com/mygroup/mysubgroup/mysubsubgroup/myproject", + openssl, + ), + ] } #[test] -#[ignore = "GitLabDriver tests mock HttpDownloader/IO (curl_multi_init todo!()) or need setUp gitlab-domains config"] +#[ignore = "HttpDownloaderMock (getHttpDownloaderMock) and the IOInterface MockObject are not ported"] fn test_gitlab_sub_directory() { todo!() } #[test] -#[ignore = "GitLabDriver tests mock HttpDownloader/IO (curl_multi_init todo!()) or need setUp gitlab-domains config"] +#[ignore = "HttpDownloaderMock (getHttpDownloaderMock) and the IOInterface MockObject are not ported"] fn test_gitlab_sub_group() { todo!() } #[test] -#[ignore = "GitLabDriver tests mock HttpDownloader/IO (curl_multi_init todo!()) or need setUp gitlab-domains config"] +#[ignore = "HttpDownloaderMock (getHttpDownloaderMock) and the IOInterface MockObject are not ported"] fn test_gitlab_sub_directory_sub_group() { todo!() } #[test] -#[ignore = "GitLabDriver tests mock HttpDownloader/IO (curl_multi_init todo!()) or need setUp gitlab-domains config"] +#[ignore = "HttpDownloaderMock (getHttpDownloaderMock) and the IOInterface MockObject are not ported"] fn test_forwards_options() { todo!() } #[test] -#[ignore = "GitLabDriver tests mock HttpDownloader/IO (curl_multi_init todo!()) or need setUp gitlab-domains config"] +#[ignore = "HttpDownloaderMock (getHttpDownloaderMock) and the IOInterface MockObject are not ported"] fn test_protocol_override_repository_url_generation() { todo!() } diff --git a/crates/shirabe/tests/repository/vcs/hg_driver_test.rs b/crates/shirabe/tests/repository/vcs/hg_driver_test.rs index 155d7ce..7b6f1e6 100644 --- a/crates/shirabe/tests/repository/vcs/hg_driver_test.rs +++ b/crates/shirabe/tests/repository/vcs/hg_driver_test.rs @@ -80,7 +80,7 @@ fn test_supports() { // (curl_multi_init is todo!() in the php-shim) and a mocked ProcessExecutor to feed // hg command output, neither of which is available here. #[test] -#[ignore = "needs an HgDriver instance (HttpDownloader reaches curl_multi_init, todo!()) and a mocked ProcessExecutor"] +#[ignore = "requires getProcessExecutorMock with expects() hg branches/bookmarks command-sequence assertions and a getMockBuilder HttpDownloader mock; no ProcessExecutorMock/HttpDownloader mocking infrastructure exists"] fn test_get_branches_filter_invalid_branch_names() { let SetUp { home, config, io } = set_up(); let _tear_down = TearDown::new(home.path().to_path_buf()); @@ -89,7 +89,7 @@ fn test_get_branches_filter_invalid_branch_names() { } #[test] -#[ignore = "needs an HgDriver instance (HttpDownloader reaches curl_multi_init, todo!()) and a mocked ProcessExecutor"] +#[ignore = "requires getProcessExecutorMock and a getMockBuilder HttpDownloader mock to construct HgDriver; no ProcessExecutorMock/HttpDownloader mocking infrastructure exists"] fn test_file_get_content_invalid_identifier() { let SetUp { home, config, io } = set_up(); let _tear_down = TearDown::new(home.path().to_path_buf()); @@ -98,7 +98,7 @@ fn test_file_get_content_invalid_identifier() { } #[test] -#[ignore = "needs an HgDriver instance (HttpDownloader reaches curl_multi_init, todo!()) and a mocked ProcessExecutor"] +#[ignore = "requires getProcessExecutorMock and a getMockBuilder HttpDownloader mock to construct HgDriver; no ProcessExecutorMock/HttpDownloader mocking infrastructure exists"] fn test_get_change_date_invalid_identifier() { let SetUp { home, config, io } = set_up(); let _tear_down = TearDown::new(home.path().to_path_buf()); diff --git a/crates/shirabe/tests/repository/vcs/perforce_driver_test.rs b/crates/shirabe/tests/repository/vcs/perforce_driver_test.rs index 1c28487..d9178dd 100644 --- a/crates/shirabe/tests/repository/vcs/perforce_driver_test.rs +++ b/crates/shirabe/tests/repository/vcs/perforce_driver_test.rs @@ -108,8 +108,8 @@ fn test_supports_returns_false_no_deep_check() { // The remaining cases mock Perforce, the repository config and IO to drive initialization, // composer-file detection and cleanup; mocking is not available here. +#[ignore] #[test] -#[ignore = "mocks Perforce/repository/IO; mocking is not available"] fn test_initialize_captures_variables_from_repo_config() { let SetUp { test_path, @@ -122,20 +122,29 @@ fn test_initialize_captures_variables_from_repo_config() { driver, } = set_up(); let _tear_down = TearDown::new(test_path.path().to_path_buf()); - let _ = ( - &config, - &repo_config, - &io, - &process, - &http_downloader, - &perforce, - &driver, - ); - todo!() + let _ = (&io, &process, &http_downloader, &perforce, &driver); + + let io: Rc<RefCell<dyn IOInterface>> = Rc::new(RefCell::new(NullIO::new())); + let config = Rc::new(RefCell::new(config)); + let http_downloader = Rc::new(RefCell::new(shirabe::util::HttpDownloader::new( + io.clone(), + config.clone(), + IndexMap::new(), + false, + ))); + let process = Rc::new(RefCell::new(shirabe::util::ProcessExecutor::new(Some( + io.clone(), + )))); + + let mut driver = PerforceDriver::new(repo_config, io, config, http_downloader, process); + driver.initialize().unwrap(); + assert_eq!(TEST_URL, driver.get_url()); + assert_eq!(TEST_DEPOT, driver.get_depot()); + assert_eq!(TEST_BRANCH, driver.get_branch()); } +#[ignore = "needs a Perforce mock injected via reflection (overrideDriverInternalPerforce) to assert p4Login/checkStream/writeP4ClientSpec/connectClient are each called once; the perforce field is pub(crate) and no mock infra exists"] #[test] -#[ignore = "mocks Perforce/repository/IO; mocking is not available"] fn test_initialize_logs_in_and_connects_client() { let SetUp { test_path, @@ -160,8 +169,8 @@ fn test_initialize_logs_in_and_connects_client() { todo!() } +#[ignore = "needs a Perforce mock injected via reflection (overrideDriverInternalPerforce) to stub getComposerInformation; the perforce field is pub(crate) and no mock infra exists"] #[test] -#[ignore = "mocks Perforce/repository/IO; mocking is not available"] fn test_has_composer_file_returns_false_on_no_composer_file() { let SetUp { test_path, @@ -186,8 +195,8 @@ fn test_has_composer_file_returns_false_on_no_composer_file() { todo!() } +#[ignore = "needs a Perforce mock injected via reflection (overrideDriverInternalPerforce) to stub getComposerInformation; the perforce field is pub(crate) and no mock infra exists"] #[test] -#[ignore = "mocks Perforce/repository/IO; mocking is not available"] fn test_has_composer_file_returns_true_with_one_or_more_composer_files() { let SetUp { test_path, @@ -212,8 +221,8 @@ fn test_has_composer_file_returns_true_with_one_or_more_composer_files() { todo!() } +#[ignore = "needs a Perforce mock injected via reflection (overrideDriverInternalPerforce) to assert cleanupClientSpec is called once; the perforce field is pub(crate) and no mock infra exists"] #[test] -#[ignore = "mocks Perforce/repository/IO; mocking is not available"] fn test_cleanup() { let SetUp { test_path, diff --git a/crates/shirabe/tests/repository/vcs/svn_driver_test.rs b/crates/shirabe/tests/repository/vcs/svn_driver_test.rs index 9625088..5ec9097 100644 --- a/crates/shirabe/tests/repository/vcs/svn_driver_test.rs +++ b/crates/shirabe/tests/repository/vcs/svn_driver_test.rs @@ -77,8 +77,8 @@ fn test_support() { // Constructs an SvnDriver and runs an svn command via a mocked ProcessExecutor; mocking is // not available here. +#[ignore = "requires ProcessExecutor mock (getProcessExecutorMock/expects) and IOInterface/HttpDownloader mocks, none available"] #[test] -#[ignore = "constructs an SvnDriver and mocks a ProcessExecutor for the svn invocation"] fn test_wrong_credentials_in_url() { let SetUp { home, config } = set_up(); let _tear_down = TearDown::new(home.path().to_path_buf()); diff --git a/crates/shirabe/tests/repository/vcs_repository_test.rs b/crates/shirabe/tests/repository/vcs_repository_test.rs index c687b3d..d84ed51 100644 --- a/crates/shirabe/tests/repository/vcs_repository_test.rs +++ b/crates/shirabe/tests/repository/vcs_repository_test.rs @@ -1,6 +1,18 @@ //! ref: composer/tests/Composer/Test/Repository/VcsRepositoryTest.php +use std::cell::RefCell; +use std::rc::Rc; + +use indexmap::IndexMap; +use shirabe::config::Config; +use shirabe::io::IOInterface; +use shirabe::io::null_io::NullIO; +use shirabe::package::dumper::ArrayDumper; +use shirabe::repository::RepositoryInterface; +use shirabe::repository::VcsRepository; use shirabe::util::filesystem::Filesystem; +use shirabe::util::http_downloader::HttpDownloader; +use shirabe_php_shim::PhpMixed; struct SetUp { composer_home: std::path::PathBuf, @@ -42,14 +54,11 @@ impl Drop for TearDown { // testLoadVersions initialises a real git repository on disk and drives a VcsRepository over // it, then asserts the loaded package versions; the git fixture setup and constraint parsing -// (look-around regex) are not ported. +// (look-around regex) are not ported. In addition, VcsRepository does not implement +// RepositoryInterface and keeps its inner ArrayRepository pub(crate), so getPackages() (inherited +// from ArrayRepository in PHP) is not reachable from the test crate. #[test] -#[ignore = "not yet ported (initialises a git repo on disk and loads versions; constraint parsing uses a look-around regex)"] +#[ignore = "VcsRepository does not expose get_packages() to the test crate; git fixture setup not ported"] fn test_load_versions() { - let SetUp { - composer_home, - git_repo, - } = set_up(); - let _tear_down = TearDown::new(composer_home, git_repo); todo!() } diff --git a/crates/shirabe/tests/script/event_test.rs b/crates/shirabe/tests/script/event_test.rs index e2aab3c..b045bc4 100644 --- a/crates/shirabe/tests/script/event_test.rs +++ b/crates/shirabe/tests/script/event_test.rs @@ -60,7 +60,7 @@ fn test_event_sets_originating_event() { // Event cannot be passed as the originating event (it is not polymorphic), and // the test cannot be expressed faithfully. #[test] -#[ignore = "set_originating_event takes a concrete event_dispatcher::Event; a Script Event cannot be passed as originating event, and calculate_originating_event is todo!()"] +#[ignore = "Event::set_originating_event takes a concrete event_dispatcher::Event, so a nested script::Event cannot be passed as originating event; the recursive calculate_originating_event behavior is not expressible"] fn test_event_calculates_nested_originating_event() { todo!() } diff --git a/crates/shirabe/tests/util/auth_helper_test.rs b/crates/shirabe/tests/util/auth_helper_test.rs index 9eaeac1..21bf1c6 100644 --- a/crates/shirabe/tests/util/auth_helper_test.rs +++ b/crates/shirabe/tests/util/auth_helper_test.rs @@ -10,121 +10,121 @@ fn set_up() { } #[test] -#[ignore = "mocks IO/Config to drive AuthHelper; mocking is not available"] +#[ignore = "requires PHPUnit mock IOInterface with expects()/willReturn(); no mocking framework"] fn test_add_authentication_header_without_auth_credentials() { todo!() } #[test] -#[ignore = "mocks IO/Config to drive AuthHelper; mocking is not available"] +#[ignore = "requires PHPUnit mock IOInterface with expects()/willReturn(); no mocking framework"] fn test_add_authentication_header_with_bearer_password() { todo!() } #[test] -#[ignore = "mocks IO/Config to drive AuthHelper; mocking is not available"] +#[ignore = "requires PHPUnit mock IOInterface with expects()/willReturn(); no mocking framework"] fn test_add_authentication_header_with_github_token() { todo!() } #[test] -#[ignore = "mocks IO/Config to drive AuthHelper; mocking is not available"] +#[ignore = "requires PHPUnit mock IOInterface/Config with expects()/willReturn(); no mocking framework"] fn test_add_authentication_header_with_gitlab_oath_token() { todo!() } #[test] -#[ignore = "mocks IO/Config to drive AuthHelper; mocking is not available"] +#[ignore = "requires PHPUnit mock IOInterface with expects()/willReturn(); no mocking framework"] fn test_add_authentication_options_for_client_certificate() { todo!() } #[test] -#[ignore = "mocks IO/Config to drive AuthHelper; mocking is not available"] +#[ignore = "requires PHPUnit mock IOInterface/Config with expects()/willReturn(); no mocking framework"] fn test_add_authentication_header_with_gitlab_private_token() { todo!() } #[test] -#[ignore = "mocks IO/Config to drive AuthHelper; mocking is not available"] +#[ignore = "requires PHPUnit mock IOInterface with expects()/willReturn(); no mocking framework"] fn test_add_authentication_header_with_bitbucket_oath_token() { todo!() } #[test] -#[ignore = "mocks IO/Config to drive AuthHelper; mocking is not available"] +#[ignore = "requires PHPUnit mock IOInterface with expects()/willReturn(); no mocking framework"] fn test_add_authentication_header_with_bitbucket_public_url() { todo!() } #[test] -#[ignore = "mocks IO/Config to drive AuthHelper; mocking is not available"] +#[ignore = "requires PHPUnit mock IOInterface with expects()/willReturn(); no mocking framework"] fn test_add_authentication_header_with_basic_http_authentication() { todo!() } #[test] -#[ignore = "mocks IO/Config to drive AuthHelper; mocking is not available"] +#[ignore = "requires PHPUnit mock IOInterface with expects()/willReturn(); no mocking framework"] fn test_add_authentication_header_with_custom_headers() { todo!() } #[test] -#[ignore = "mocks IO/Config to drive AuthHelper; mocking is not available"] +#[ignore = "requires PHPUnit mock IOInterface/Config in setUp; no mocking framework"] fn test_is_public_bit_bucket_download_with_bitbucket_public_url() { todo!() } #[test] -#[ignore = "mocks IO/Config to drive AuthHelper; mocking is not available"] +#[ignore = "requires PHPUnit mock IOInterface/Config in setUp; no mocking framework"] fn test_is_public_bit_bucket_download_with_non_bitbucket_public_url() { todo!() } #[test] -#[ignore = "mocks IO/Config to drive AuthHelper; mocking is not available"] +#[ignore = "requires PHPUnit mock Config/ConfigSourceInterface with expects()/willReturn(); no mocking framework"] fn test_store_auth_automatically() { todo!() } #[test] -#[ignore = "mocks IO/Config to drive AuthHelper; mocking is not available"] +#[ignore = "requires PHPUnit mock IOInterface/Config/ConfigSourceInterface with willReturnCallback(); no mocking framework"] fn test_store_auth_with_prompt_yes_answer() { todo!() } #[test] -#[ignore = "mocks IO/Config to drive AuthHelper; mocking is not available"] +#[ignore = "requires PHPUnit mock IOInterface/Config/ConfigSourceInterface with willReturnCallback(); no mocking framework"] fn test_store_auth_with_prompt_no_answer() { todo!() } #[test] -#[ignore = "mocks IO/Config to drive AuthHelper; mocking is not available"] +#[ignore = "requires PHPUnit mock IOInterface/Config/ConfigSourceInterface with willReturnCallback(); no mocking framework"] fn test_store_auth_with_prompt_invalid_answer() { todo!() } #[test] -#[ignore = "mocks IO/Config to drive AuthHelper; mocking is not available"] +#[ignore = "requires PHPUnit mock IOInterface/Config with expects()/willReturnMap(); no mocking framework"] fn test_prompt_auth_if_needed_git_lab_no_auth_change() { todo!() } #[test] -#[ignore = "mocks IO/Config to drive AuthHelper; mocking is not available"] +#[ignore = "requires PHPUnit mock IOInterface/Config with willReturnMap()/willReturnCallback(); no mocking framework"] fn test_prompt_auth_if_needed_multiple_bitbucket_downloads() { todo!() } #[test] -#[ignore = "mocks IO/Config to drive AuthHelper; mocking is not available"] +#[ignore = "requires PHPUnit mock IOInterface with expects()/willReturn() plus set_error_handler; no mocking framework"] fn test_add_authentication_header_is_working() { todo!() } #[test] -#[ignore = "mocks IO/Config to drive AuthHelper; mocking is not available"] +#[ignore = "requires PHPUnit mock IOInterface in setUp plus set_error_handler deprecation capture; no mocking framework"] fn test_add_authentication_header_deprecation() { todo!() } diff --git a/crates/shirabe/tests/util/bitbucket_test.rs b/crates/shirabe/tests/util/bitbucket_test.rs index 4087294..3e3fad8 100644 --- a/crates/shirabe/tests/util/bitbucket_test.rs +++ b/crates/shirabe/tests/util/bitbucket_test.rs @@ -10,91 +10,91 @@ fn set_up() { } #[test] -#[ignore = "mocks IO/Config/HttpDownloader (curl_multi_init todo!()) for the Bitbucket OAuth flow"] +#[ignore = "needs getIOMock/IOMock and getMockBuilder mocks for HttpDownloader/Config (no mock infrastructure)"] fn test_request_access_token_with_valid_oauth_consumer() { todo!() } #[test] -#[ignore = "mocks IO/Config/HttpDownloader (curl_multi_init todo!()) for the Bitbucket OAuth flow"] +#[ignore = "needs getMockBuilder mock for Config (no mock infrastructure)"] fn test_request_access_token_with_valid_oauth_consumer_and_valid_stored_access_token() { todo!() } #[test] -#[ignore = "mocks IO/Config/HttpDownloader (curl_multi_init todo!()) for the Bitbucket OAuth flow"] +#[ignore = "needs getIOMock/IOMock and getMockBuilder mocks for HttpDownloader/Config (no mock infrastructure)"] fn test_request_access_token_with_valid_oauth_consumer_and_expired_access_token() { todo!() } #[test] -#[ignore = "mocks IO/Config/HttpDownloader (curl_multi_init todo!()) for the Bitbucket OAuth flow"] +#[ignore = "needs getIOMock/IOMock and getMockBuilder mocks for HttpDownloader/Config (no mock infrastructure)"] fn test_request_access_token_with_username_and_password() { todo!() } #[test] -#[ignore = "mocks IO/Config/HttpDownloader (curl_multi_init todo!()) for the Bitbucket OAuth flow"] +#[ignore = "needs getIOMock/IOMock and getMockBuilder mocks for HttpDownloader/Config (no mock infrastructure)"] fn test_request_access_token_with_username_and_password_with_unauthorized_response() { todo!() } #[test] -#[ignore = "mocks IO/Config/HttpDownloader (curl_multi_init todo!()) for the Bitbucket OAuth flow"] +#[ignore = "needs getIOMock/IOMock and getMockBuilder mocks for HttpDownloader/Config (no mock infrastructure)"] fn test_request_access_token_with_username_and_password_with_not_found_response() { todo!() } #[test] -#[ignore = "mocks IO/Config/HttpDownloader (curl_multi_init todo!()) for the Bitbucket OAuth flow"] +#[ignore = "needs getIOMock/IOMock and getMockBuilder mocks for HttpDownloader/Config (no mock infrastructure)"] fn test_username_password_authentication_flow() { todo!() } #[test] -#[ignore = "mocks IO/Config/HttpDownloader (curl_multi_init todo!()) for the Bitbucket OAuth flow"] +#[ignore = "needs getIOMock/IOMock and getMockBuilder mock for Config (no mock infrastructure)"] fn test_authorize_oauth_interactively_with_empty_username() { todo!() } #[test] -#[ignore = "mocks IO/Config/HttpDownloader (curl_multi_init todo!()) for the Bitbucket OAuth flow"] +#[ignore = "needs getIOMock/IOMock and getMockBuilder mock for Config (no mock infrastructure)"] fn test_authorize_oauth_interactively_with_empty_password() { todo!() } #[test] -#[ignore = "mocks IO/Config/HttpDownloader (curl_multi_init todo!()) for the Bitbucket OAuth flow"] +#[ignore = "needs getIOMock/IOMock and getMockBuilder mocks for HttpDownloader/Config (no mock infrastructure)"] fn test_authorize_oauth_interactively_with_request_access_token_failure() { todo!() } #[test] -#[ignore = "mocks IO/Config/HttpDownloader (curl_multi_init todo!()) for the Bitbucket OAuth flow"] +#[ignore = "needs getIOMock/IOMock and getMockBuilder mocks for HttpDownloader/Config to construct Bitbucket (no mock infrastructure)"] fn test_get_token_without_access_token() { todo!() } #[test] -#[ignore = "mocks IO/Config/HttpDownloader (curl_multi_init todo!()) for the Bitbucket OAuth flow"] +#[ignore = "needs getMockBuilder mock for Config and @depends-injected Bitbucket from a mock-based test (no mock infrastructure)"] fn test_get_token_with_access_token() { todo!() } #[test] -#[ignore = "mocks IO/Config/HttpDownloader (curl_multi_init todo!()) for the Bitbucket OAuth flow"] +#[ignore = "needs getIOMock/IOMock and getMockBuilder mocks for HttpDownloader/Config to construct Bitbucket (no mock infrastructure)"] fn test_authorize_oauth_with_wrong_origin_url() { todo!() } #[test] -#[ignore = "mocks IO/Config/HttpDownloader (curl_multi_init todo!()) for the Bitbucket OAuth flow"] +#[ignore = "needs getIOMock/IOMock, getProcessExecutorMock and getMockBuilder mocks for HttpDownloader/Config (no mock infrastructure)"] fn test_authorize_oauth_without_available_git_config_token() { todo!() } #[test] -#[ignore = "mocks IO/Config/HttpDownloader (curl_multi_init todo!()) for the Bitbucket OAuth flow"] +#[ignore = "needs getIOMock/IOMock, getProcessExecutorMock and getMockBuilder mocks for HttpDownloader/Config (no mock infrastructure)"] fn test_authorize_oauth_with_available_git_config_token() { todo!() } diff --git a/crates/shirabe/tests/util/error_handler_test.rs b/crates/shirabe/tests/util/error_handler_test.rs index 8f42bca..7295cf2 100644 --- a/crates/shirabe/tests/util/error_handler_test.rs +++ b/crates/shirabe/tests/util/error_handler_test.rs @@ -26,20 +26,20 @@ impl Drop for TearDown { } } +#[ignore = "depends on PHP runtime routing an undefined-index notice through set_error_handler; no Rust equivalent for $array['baz'] triggering ErrorHandler::handle"] #[test] -#[ignore = "relies on PHP's set_error_handler converting an undefined-array-key notice into an ErrorException; no Rust equivalent"] fn test_error_handler_capture_notice() { todo!() } +#[ignore = "depends on PHP runtime emitting a TypeError/warning from array_merge([], 'string') via set_error_handler; no Rust equivalent"] #[test] -#[ignore = "relies on PHP's runtime turning an invalid array_merge call into a TypeError/ErrorException; no Rust equivalent"] fn test_error_handler_capture_warning() { todo!() } +#[ignore = "depends on the PHP @ error-suppression operator and trigger_error routing through set_error_handler; no Rust equivalent"] #[test] -#[ignore = "relies on PHP's @ error-suppression operator and set_error_handler; no Rust equivalent"] fn test_error_handler_respects_at_operator() { todo!() } diff --git a/crates/shirabe/tests/util/filesystem_test.rs b/crates/shirabe/tests/util/filesystem_test.rs index 39fa256..9e41b1f 100644 --- a/crates/shirabe/tests/util/filesystem_test.rs +++ b/crates/shirabe/tests/util/filesystem_test.rs @@ -1,10 +1,13 @@ //! ref: composer/tests/Composer/Test/Util/FilesystemTest.php // These exercise Filesystem path helpers and on-disk operations (sizes, copy, symlinks and -// junctions over a temp tree). The filesystem fixtures and platform-specific symlink/junction -// behaviour are not ported. +// junctions over a temp tree). The on-disk tests build their temp tree with tempfile::TempDir in +// place of TestCase::getUniqueTmpDirectory, which is not ported. use shirabe::util::filesystem::Filesystem; -use shirabe_php_shim::{dirname, is_dir, is_file}; +use shirabe::util::platform::Platform; +use shirabe_php_shim::{ + dirname, file_exists, file_put_contents, is_dir, is_file, mkdir, symlink, touch, +}; #[allow(dead_code)] struct SetUp { @@ -51,74 +54,769 @@ impl Drop for TearDown { } } +/// providePathCouplesAsCode: (a, b, directory, expected, static, prefer_relative) +fn provide_path_couples_as_code() +-> Vec<(&'static str, &'static str, bool, &'static str, bool, bool)> { + vec![ + ("/foo/bar", "/foo/bar", false, "__FILE__", false, false), + ( + "/foo/bar", + "/foo/baz", + false, + "__DIR__.'/baz'", + false, + false, + ), + ( + "/foo/bin/run", + "/foo/vendor/acme/bin/run", + false, + "dirname(__DIR__).'/vendor/acme/bin/run'", + false, + false, + ), + ( + "/foo/bin/run", + "/bar/bin/run", + false, + "'/bar/bin/run'", + false, + false, + ), + ( + "c:/bin/run", + "c:/vendor/acme/bin/run", + false, + "dirname(__DIR__).'/vendor/acme/bin/run'", + false, + false, + ), + ( + "c:\\bin\\run", + "c:/vendor/acme/bin/run", + false, + "dirname(__DIR__).'/vendor/acme/bin/run'", + false, + false, + ), + ( + "c:/bin/run", + "D:/vendor/acme/bin/run", + false, + "'D:/vendor/acme/bin/run'", + false, + false, + ), + ( + "c:\\bin\\run", + "d:/vendor/acme/bin/run", + false, + "'D:/vendor/acme/bin/run'", + false, + false, + ), + ("/foo/bar", "/foo/bar", true, "__DIR__", false, false), + ("/foo/bar/", "/foo/bar", true, "__DIR__", false, false), + ( + "/foo", + "/baz", + true, + "dirname(__DIR__).'/baz'", + false, + false, + ), + ( + "/foo/bar", + "/foo/baz", + true, + "dirname(__DIR__).'/baz'", + false, + false, + ), + ( + "/foo/bin/run", + "/foo/vendor/acme/bin/run", + true, + "dirname(dirname(__DIR__)).'/vendor/acme/bin/run'", + false, + false, + ), + ( + "/foo/bin/run", + "/bar/bin/run", + true, + "'/bar/bin/run'", + false, + false, + ), + ( + "/app/vendor/foo/bar", + "/lib", + true, + "dirname(dirname(dirname(dirname(__DIR__)))).'/lib'", + false, + true, + ), + ("/bin/run", "/bin/run", true, "__DIR__", false, false), + ("c:/bin/run", "C:\\bin/run", true, "__DIR__", false, false), + ( + "c:/bin/run", + "c:/vendor/acme/bin/run", + true, + "dirname(dirname(__DIR__)).'/vendor/acme/bin/run'", + false, + false, + ), + ( + "c:\\bin\\run", + "c:/vendor/acme/bin/run", + true, + "dirname(dirname(__DIR__)).'/vendor/acme/bin/run'", + false, + false, + ), + ( + "c:/bin/run", + "d:/vendor/acme/bin/run", + true, + "'D:/vendor/acme/bin/run'", + false, + false, + ), + ( + "c:\\bin\\run", + "d:/vendor/acme/bin/run", + true, + "'D:/vendor/acme/bin/run'", + false, + false, + ), + ( + "C:/Temp/test", + "C:\\Temp", + true, + "dirname(__DIR__)", + false, + false, + ), + ( + "C:/Temp", + "C:\\Temp\\test", + true, + "__DIR__ . '/test'", + false, + false, + ), + ("/tmp/test", "/tmp", true, "dirname(__DIR__)", false, false), + ("/tmp", "/tmp/test", true, "__DIR__ . '/test'", false, false), + ( + "C:/Temp", + "c:\\Temp\\test", + true, + "__DIR__ . '/test'", + false, + false, + ), + ("/tmp/test/./", "/tmp/test/", true, "__DIR__", false, false), + ( + "/tmp/test/../vendor", + "/tmp/test", + true, + "dirname(__DIR__).'/test'", + false, + false, + ), + ( + "/tmp/test/.././vendor", + "/tmp/test", + true, + "dirname(__DIR__).'/test'", + false, + false, + ), + ( + "C:/Temp", + "c:\\Temp\\..\\..\\test", + true, + "dirname(__DIR__).'/test'", + false, + false, + ), + ( + "C:/Temp/../..", + "d:\\Temp\\..\\..\\test", + true, + "'D:/test'", + false, + false, + ), + ( + "/foo/bar", + "/foo/bar_vendor", + true, + "dirname(__DIR__).'/bar_vendor'", + false, + false, + ), + ( + "/foo/bar_vendor", + "/foo/bar", + true, + "dirname(__DIR__).'/bar'", + false, + false, + ), + ( + "/foo/bar_vendor", + "/foo/bar/src", + true, + "dirname(__DIR__).'/bar/src'", + false, + false, + ), + ( + "/foo/bar_vendor/src2", + "/foo/bar/src/lib", + true, + "dirname(dirname(__DIR__)).'/bar/src/lib'", + false, + false, + ), + // static use case + ( + "/tmp/test/../vendor", + "/tmp/test", + true, + "__DIR__ . '/..'.'/test'", + true, + false, + ), + ( + "/tmp/test/.././vendor", + "/tmp/test", + true, + "__DIR__ . '/..'.'/test'", + true, + false, + ), + ( + "C:/Temp", + "c:\\Temp\\..\\..\\test", + true, + "__DIR__ . '/..'.'/test'", + true, + false, + ), + ( + "C:/Temp/../..", + "d:\\Temp\\..\\..\\test", + true, + "'D:/test'", + true, + false, + ), + ( + "/foo/bar", + "/foo/bar_vendor", + true, + "__DIR__ . '/..'.'/bar_vendor'", + true, + false, + ), + ( + "/foo/bar_vendor", + "/foo/bar", + true, + "__DIR__ . '/..'.'/bar'", + true, + false, + ), + ( + "/foo/bar_vendor", + "/foo/bar/src", + true, + "__DIR__ . '/..'.'/bar/src'", + true, + false, + ), + ( + "/foo/bar_vendor/src2", + "/foo/bar/src/lib", + true, + "__DIR__ . '/../..'.'/bar/src/lib'", + true, + false, + ), + ] +} + +#[ignore] #[test] -#[ignore = "not yet ported (Filesystem path helpers plus on-disk size/copy/symlink/junction fixtures)"] fn test_find_shortest_path_code() { - todo!() + let fs = Filesystem::new(None); + for (a, b, directory, expected, static_code, prefer_relative) in provide_path_couples_as_code() + { + assert_eq!( + expected, + fs.find_shortest_path_code(a, b, directory, static_code, prefer_relative) + ); + } } +/// providePathCouples: (a, b, expected, directory, prefer_relative) +fn provide_path_couples() -> Vec<(&'static str, &'static str, &'static str, bool, bool)> { + vec![ + ("/foo/bar", "/foo/bar", "./bar", false, false), + ("/foo/bar", "/foo/baz", "./baz", false, false), + ("/foo/bar/", "/foo/baz", "./baz", false, false), + ("/foo/bar", "/foo/bar", "./", true, false), + ("/foo/bar", "/foo/baz", "../baz", true, false), + ("/foo/bar/", "/foo/baz", "../baz", true, false), + ("C:/foo/bar/", "c:/foo/baz", "../baz", true, false), + ( + "/foo/bin/run", + "/foo/vendor/acme/bin/run", + "../vendor/acme/bin/run", + false, + false, + ), + ("/foo/bin/run", "/bar/bin/run", "/bar/bin/run", false, false), + ("/foo/bin/run", "/bar/bin/run", "/bar/bin/run", true, false), + ( + "c:/foo/bin/run", + "d:/bar/bin/run", + "D:/bar/bin/run", + true, + false, + ), + ( + "c:/bin/run", + "c:/vendor/acme/bin/run", + "../vendor/acme/bin/run", + false, + false, + ), + ( + "c:\\bin\\run", + "c:/vendor/acme/bin/run", + "../vendor/acme/bin/run", + false, + false, + ), + ( + "c:/bin/run", + "d:/vendor/acme/bin/run", + "D:/vendor/acme/bin/run", + false, + false, + ), + ( + "c:\\bin\\run", + "d:/vendor/acme/bin/run", + "D:/vendor/acme/bin/run", + false, + false, + ), + ("C:/Temp/test", "C:\\Temp", "./", false, false), + ("/tmp/test", "/tmp", "./", false, false), + ("C:/Temp/test/sub", "C:\\Temp", "../", false, false), + ("/tmp/test/sub", "/tmp", "../", false, false), + ("/tmp/test/sub", "/tmp", "../../", true, false), + ("c:/tmp/test/sub", "c:/tmp", "../../", true, false), + ("/tmp", "/tmp/test", "test", false, false), + ("C:/Temp", "C:\\Temp\\test", "test", false, false), + ("C:/Temp", "c:\\Temp\\test", "test", false, false), + ("/tmp/test/./", "/tmp/test", "./", true, false), + ("/tmp/test/../vendor", "/tmp/test", "../test", true, false), + ("/tmp/test/.././vendor", "/tmp/test", "../test", true, false), + ("C:/Temp", "c:\\Temp\\..\\..\\test", "../test", true, false), + ( + "C:/Temp/../..", + "c:\\Temp\\..\\..\\test", + "./test", + true, + false, + ), + ( + "C:/Temp/../..", + "D:\\Temp\\..\\..\\test", + "D:/test", + true, + false, + ), + ("/app/vendor/foo/bar", "/lib", "../../../../lib", true, true), + ("/tmp", "/tmp/../../test", "../test", true, false), + ("/tmp", "/test", "../test", true, false), + ("/foo/bar", "/foo/bar_vendor", "../bar_vendor", true, false), + ("/foo/bar_vendor", "/foo/bar", "../bar", true, false), + ("/foo/bar_vendor", "/foo/bar/src", "../bar/src", true, false), + ( + "/foo/bar_vendor/src2", + "/foo/bar/src/lib", + "../../bar/src/lib", + true, + false, + ), + ("C:/", "C:/foo/bar/", "foo/bar", true, false), + ] +} + +#[ignore] #[test] -#[ignore = "not yet ported (Filesystem path helpers plus on-disk size/copy/symlink/junction fixtures)"] fn test_find_shortest_path() { - todo!() + let fs = Filesystem::new(None); + for (a, b, expected, directory, prefer_relative) in provide_path_couples() { + assert_eq!( + expected, + fs.find_shortest_path(a, b, directory, prefer_relative) + ); + } } +#[ignore] #[test] -#[ignore = "not yet ported (Filesystem path helpers plus on-disk size/copy/symlink/junction fixtures)"] fn test_remove_directory_php() { - todo!() + let working_dir = tempfile::TempDir::new().unwrap(); + let working_dir = working_dir.path().to_str().unwrap().to_string(); + + mkdir(&format!("{working_dir}/level1/level2"), 0o777, true); + file_put_contents( + &format!("{working_dir}/level1/level2/hello.txt"), + b"hello world", + ); + + let mut fs = Filesystem::new(None); + assert!(fs.remove_directory_php(&working_dir).unwrap()); + assert!(!file_exists(format!( + "{working_dir}/level1/level2/hello.txt" + ))); } +#[ignore] #[test] -#[ignore = "not yet ported (Filesystem path helpers plus on-disk size/copy/symlink/junction fixtures)"] fn test_file_size() { - todo!() + let unique_tmp = tempfile::TempDir::new().unwrap(); + let test_file = format!("{}/composer_test_file", unique_tmp.path().to_str().unwrap()); + + file_put_contents(&test_file, b"Hello"); + + let fs = Filesystem::new(None); + assert!(fs.size(&test_file).unwrap() >= 5); } +#[ignore] #[test] -#[ignore = "not yet ported (Filesystem path helpers plus on-disk size/copy/symlink/junction fixtures)"] fn test_directory_size() { - todo!() + let working_dir = tempfile::TempDir::new().unwrap(); + let working_dir = working_dir.path().to_str().unwrap().to_string(); + + mkdir(&working_dir, 0o777, true); + file_put_contents(&format!("{working_dir}/file1.txt"), b"Hello"); + file_put_contents(&format!("{working_dir}/file2.txt"), b"World"); + + let fs = Filesystem::new(None); + assert!(fs.size(&working_dir).unwrap() >= 10); } +/// provideNormalizedPaths: (expected, actual) +fn provide_normalized_paths() -> Vec<(&'static str, &'static str)> { + vec![ + ("../foo", "../foo"), + ("C:/foo/bar", "c:/foo//bar"), + ("C:/foo/bar", "C:/foo/./bar"), + ("C:/foo/bar", "C://foo//bar"), + ("C:/foo/bar", "C:///foo//bar"), + ("C:/bar", "C:/foo/../bar"), + ("/bar", "/foo/../bar/"), + ("phar://C:/Foo", "phar://c:/Foo/Bar/.."), + ("phar://C:/Foo", "phar://c:///Foo/Bar/.."), + ("phar://C:/", "phar://c:/Foo/Bar/../../../.."), + ("/", "/Foo/Bar/../../../.."), + ("/", "/"), + ("/", "//"), + ("/", "///"), + ("/Foo", "///Foo"), + ("C:/", "c:\\"), + ("../src", "Foo/Bar/../../../src"), + ("C:../b", "c:.\\..\\a\\..\\b"), + ("phar://C:../Foo", "phar://c:../Foo"), + ("//foo/bar", "\\\\foo\\bar"), + ] +} + +#[ignore] #[test] -#[ignore = "not yet ported (Filesystem path helpers plus on-disk size/copy/symlink/junction fixtures)"] fn test_normalize_path() { - todo!() + let fs = Filesystem::new(None); + for (expected, actual) in provide_normalized_paths() { + assert_eq!(expected, fs.normalize_path(actual)); + } } +#[ignore] #[test] -#[ignore = "not yet ported (Filesystem path helpers plus on-disk size/copy/symlink/junction fixtures)"] fn test_unlink_symlinked_directory() { - todo!() + let working_dir = tempfile::TempDir::new().unwrap(); + let basepath = working_dir.path().to_str().unwrap().to_string(); + let symlinked = format!("{basepath}/linked"); + mkdir(&format!("{basepath}/real"), 0o777, true); + touch(&format!("{basepath}/real/FILE")); + + let result = symlink(&format!("{basepath}/real"), &symlinked); + + if !result { + // Symbolic links for directories not supported on this platform. + return; + } + + if !is_dir(&symlinked) { + panic!("Precondition assertion failed (is_dir is false on symbolic link to directory)."); + } + + let fs = Filesystem::new(None); + let result = fs.unlink(&symlinked).unwrap(); + assert!(result); + assert!(!file_exists(&symlinked)); } +#[ignore] #[test] -#[ignore = "not yet ported (Filesystem path helpers plus on-disk size/copy/symlink/junction fixtures)"] fn test_remove_symlinked_directory_with_trailing_slash() { - todo!() + let working_dir = tempfile::TempDir::new().unwrap(); + let working_dir = working_dir.path().to_str().unwrap().to_string(); + + mkdir(&format!("{working_dir}/real"), 0o777, true); + touch(&format!("{working_dir}/real/FILE")); + let symlinked = format!("{working_dir}/linked"); + let symlinked_trailing_slash = format!("{symlinked}/"); + + let result = symlink(&format!("{working_dir}/real"), &symlinked); + + if !result { + // Symbolic links for directories not supported on this platform. + return; + } + + if !is_dir(&symlinked) { + panic!("Precondition assertion failed (is_dir is false on symbolic link to directory)."); + } + + if !is_dir(&symlinked_trailing_slash) { + panic!("Precondition assertion failed (is_dir false w trailing slash)."); + } + + let mut fs = Filesystem::new(None); + + let result = fs.remove_directory(&symlinked_trailing_slash).unwrap(); + assert!(result); + assert!(!file_exists(&symlinked_trailing_slash)); + assert!(!file_exists(&symlinked)); } +#[ignore] #[test] -#[ignore = "not yet ported (Filesystem path helpers plus on-disk size/copy/symlink/junction fixtures)"] fn test_junctions() { - todo!() + let working_dir = tempfile::TempDir::new().unwrap(); + let working_dir = working_dir.path().to_str().unwrap().to_string(); + + mkdir(&format!("{working_dir}/real/nesting/testing"), 0o777, true); + let mut fs = Filesystem::new(None); + + // Non-Windows systems do not support this and will return false on all tests, and an exception + // on creation. + if !Platform::is_windows() { + assert!(!fs.is_junction(&working_dir)); + assert!(!fs.remove_junction(&working_dir).unwrap()); + + let target = format!("{working_dir}/real/../real/nesting"); + let junction = format!("{working_dir}/junction"); + let err = fs.junction(&target, &junction).unwrap_err(); + assert!( + err.to_string() + .contains("not available on non-Windows platform") + ); + return; + } + + let target = format!("{working_dir}/real/../real/nesting"); + let junction = format!("{working_dir}/junction"); + + // Create and detect junction + fs.junction(&target, &junction).unwrap(); + assert!(fs.is_junction(&junction), "{junction}: is a junction"); + assert!(!fs.is_junction(&target), "{target}: is not a junction"); + assert!( + fs.is_junction(&format!("{target}/../../junction")), + "{target}/../../junction: is a junction" + ); + assert!( + !fs.is_junction(&format!("{junction}/../real")), + "{junction}/../real: is not a junction" + ); + assert!( + fs.is_junction(&format!("{junction}/../junction")), + "{junction}/../junction: is a junction" + ); + + // Remove junction + assert!(is_dir(&junction), "{junction} is a directory"); + assert!( + fs.remove_junction(&junction).unwrap(), + "{junction} has been removed" + ); + assert!(!is_dir(&junction), "{junction} is not a directory"); } +#[ignore] #[test] -#[ignore = "not yet ported (Filesystem path helpers plus on-disk size/copy/symlink/junction fixtures)"] fn test_override_junctions() { - todo!() + if !Platform::is_windows() { + // Only runs on windows. + return; + } + + let working_dir = tempfile::TempDir::new().unwrap(); + let working_dir = working_dir.path().to_str().unwrap().to_string(); + + mkdir(&format!("{working_dir}/real/nesting/testing"), 0o777, true); + let mut fs = Filesystem::new(None); + + let old_target = format!("{working_dir}/real/nesting/testing"); + let target = format!("{working_dir}/real/../real/nesting"); + let junction = format!("{working_dir}/junction"); + + // Override non-broken junction + fs.junction(&old_target, &junction).unwrap(); + fs.junction(&target, &junction).unwrap(); + + assert!(fs.is_junction(&junction), "{junction}: is a junction"); + assert!( + fs.is_junction(&format!("{target}/../../junction")), + "{target}/../../junction: is a junction" + ); + + // Remove junction + assert!( + fs.remove_junction(&junction).unwrap(), + "{junction} has been removed" + ); + + // Override broken junction + fs.junction(&old_target, &junction).unwrap(); + fs.remove_directory(&old_target).unwrap(); + fs.junction(&target, &junction).unwrap(); + + assert!(fs.is_junction(&junction), "{junction}: is a junction"); + assert!( + fs.is_junction(&format!("{target}/../../junction")), + "{target}/../../junction: is a junction" + ); } +#[ignore] #[test] -#[ignore = "not yet ported (Filesystem path helpers plus on-disk size/copy/symlink/junction fixtures)"] fn test_copy() { - todo!() + let working_dir = tempfile::TempDir::new().unwrap(); + let working_dir = working_dir.path().to_str().unwrap().to_string(); + let unique_tmp = tempfile::TempDir::new().unwrap(); + let test_file = format!("{}/composer_test_file", unique_tmp.path().to_str().unwrap()); + + mkdir(&format!("{working_dir}/foo/bar"), 0o777, true); + mkdir(&format!("{working_dir}/foo/baz"), 0o777, true); + file_put_contents(&format!("{working_dir}/foo/foo.file"), b"foo"); + file_put_contents(&format!("{working_dir}/foo/bar/foobar.file"), b"foobar"); + file_put_contents(&format!("{working_dir}/foo/baz/foobaz.file"), b"foobaz"); + file_put_contents(&test_file, b"testfile"); + + let mut fs = Filesystem::new(None); + + let result1 = fs + .copy( + &format!("{working_dir}/foo"), + &format!("{working_dir}/foop"), + ) + .unwrap(); + assert!(result1, "Copying directory failed."); + assert!( + is_dir(format!("{working_dir}/foop")), + "Not a directory: {working_dir}/foop" + ); + assert!( + is_dir(format!("{working_dir}/foop/bar")), + "Not a directory: {working_dir}/foop/bar" + ); + assert!( + is_dir(format!("{working_dir}/foop/baz")), + "Not a directory: {working_dir}/foop/baz" + ); + assert!( + file_exists(format!("{working_dir}/foop/foo.file")), + "Not a file: {working_dir}/foop/foo.file" + ); + assert!( + file_exists(format!("{working_dir}/foop/bar/foobar.file")), + "Not a file: {working_dir}/foop/bar/foobar.file" + ); + assert!( + file_exists(format!("{working_dir}/foop/baz/foobaz.file")), + "Not a file: {working_dir}/foop/baz/foobaz.file" + ); + + let result2 = fs + .copy(&test_file, &format!("{working_dir}/testfile.file")) + .unwrap(); + assert!(result2); + assert!(file_exists(format!("{working_dir}/testfile.file"))); } +#[ignore] #[test] -#[ignore = "not yet ported (Filesystem path helpers plus on-disk size/copy/symlink/junction fixtures)"] fn test_copy_then_remove() { - todo!() + let working_dir = tempfile::TempDir::new().unwrap(); + let working_dir = working_dir.path().to_str().unwrap().to_string(); + let unique_tmp = tempfile::TempDir::new().unwrap(); + let test_file = format!("{}/composer_test_file", unique_tmp.path().to_str().unwrap()); + + mkdir(&format!("{working_dir}/foo/bar"), 0o777, true); + mkdir(&format!("{working_dir}/foo/baz"), 0o777, true); + file_put_contents(&format!("{working_dir}/foo/foo.file"), b"foo"); + file_put_contents(&format!("{working_dir}/foo/bar/foobar.file"), b"foobar"); + file_put_contents(&format!("{working_dir}/foo/baz/foobaz.file"), b"foobaz"); + file_put_contents(&test_file, b"testfile"); + + let mut fs = Filesystem::new(None); + + fs.copy_then_remove(&test_file, &format!("{working_dir}/testfile.file")) + .unwrap(); + assert!(!file_exists(&test_file), "Still a file: {test_file}"); + + fs.copy_then_remove( + &format!("{working_dir}/foo"), + &format!("{working_dir}/foop"), + ) + .unwrap(); + assert!( + !file_exists(format!("{working_dir}/foo/baz/foobaz.file")), + "Still a file: {working_dir}/foo/baz/foobaz.file" + ); + assert!( + !file_exists(format!("{working_dir}/foo/bar/foobar.file")), + "Still a file: {working_dir}/foo/bar/foobar.file" + ); + assert!( + !file_exists(format!("{working_dir}/foo/foo.file")), + "Still a file: {working_dir}/foo/foo.file" + ); + assert!( + !is_dir(format!("{working_dir}/foo/baz")), + "Still a directory: {working_dir}/foo/baz" + ); + assert!( + !is_dir(format!("{working_dir}/foo/bar")), + "Still a directory: {working_dir}/foo/bar" + ); + assert!( + !is_dir(format!("{working_dir}/foo")), + "Still a directory: {working_dir}/foo" + ); } diff --git a/crates/shirabe/tests/util/forgejo_test.rs b/crates/shirabe/tests/util/forgejo_test.rs index 2cae2ac..e8bf766 100644 --- a/crates/shirabe/tests/util/forgejo_test.rs +++ b/crates/shirabe/tests/util/forgejo_test.rs @@ -5,13 +5,13 @@ // available, and a real HttpDownloader reaches curl_multi_init (todo!()). #[test] -#[ignore = "mocks IO/Config/HttpDownloader for the auth flow; a real HttpDownloader reaches curl_multi_init (todo!())"] +#[ignore = "requires getIOMock/getHttpDownloaderMock and getMockBuilder mocks of Config/JsonConfigSource with expects()/willReturn(); no mocking infrastructure exists"] fn test_username_password_authentication_flow() { todo!() } #[test] -#[ignore = "mocks IO/Config/HttpDownloader for the auth flow; a real HttpDownloader reaches curl_multi_init (todo!())"] +#[ignore = "requires getIOMock/getHttpDownloaderMock and getMockBuilder mocks of Config/JsonConfigSource with expects()/willReturn(); no mocking infrastructure exists"] fn test_username_password_failure() { todo!() } diff --git a/crates/shirabe/tests/util/git_test.rs b/crates/shirabe/tests/util/git_test.rs index a78ae88..40ca9da 100644 --- a/crates/shirabe/tests/util/git_test.rs +++ b/crates/shirabe/tests/util/git_test.rs @@ -10,46 +10,46 @@ fn set_up() { } #[test] -#[ignore = "mocks IO/Config/ProcessExecutor to drive Git; mocking is not available"] +#[ignore = "requires mocked Config (getMockBuilder Config) and getProcessExecutorMock with expects() command expectations; no mocking infrastructure exists"] fn test_run_command_public_git_hub_repository_not_initial_clone() { todo!() } #[test] -#[ignore = "mocks IO/Config/ProcessExecutor to drive Git; mocking is not available"] +#[ignore = "requires mocked Config and getProcessExecutorMock with expects() command expectations; no mocking infrastructure exists"] fn test_run_command_private_git_hub_repository_not_initial_clone_not_interactive_without_authentication() { todo!() } #[test] -#[ignore = "mocks IO/Config/ProcessExecutor to drive Git; mocking is not available"] +#[ignore = "requires getProcessExecutorMock and mocked IO (getMockBuilder IOInterface with hasAuthentication/getAuthentication/isInteractive expectations); no mocking infrastructure exists"] fn test_run_command_private_git_hub_repository_not_initial_clone_not_interactive_with_authentication() { todo!() } #[test] -#[ignore = "mocks IO/Config/ProcessExecutor to drive Git; mocking is not available"] +#[ignore = "requires getProcessExecutorMock and mocked IO/Config (getMockBuilder with hasAuthentication/getAuthentication/isInteractive expectations); no mocking infrastructure exists"] fn test_run_command_private_bitbucket_repository_not_initial_clone_not_interactive_with_authentication() { todo!() } #[test] -#[ignore = "mocks IO/Config/ProcessExecutor to drive Git; mocking is not available"] +#[ignore = "requires getProcessExecutorMock, getHttpDownloaderMock and mocked IO/Config with askConfirmation/askAndHideAnswer/setAuthentication expectations; no mocking infrastructure exists"] fn test_run_command_private_bitbucket_repository_not_initial_clone_interactive_with_oauth() { todo!() } #[test] -#[ignore = "mocks IO/Config/ProcessExecutor to drive Git; mocking is not available"] +#[ignore = "requires getProcessExecutorMock with expects() command expectations and mocked Config/Filesystem (getMockBuilder removeDirectory); no mocking infrastructure exists"] fn test_sync_mirror_sanitizes_url_after_initial_clone() { todo!() } #[test] -#[ignore = "mocks IO/Config/ProcessExecutor to drive Git; mocking is not available"] +#[ignore = "requires getProcessExecutorMock with expects() command expectations and mocked Config; no mocking infrastructure exists"] fn test_sync_mirror_sanitizes_url_even_after_failed_update() { todo!() } diff --git a/crates/shirabe/tests/util/github_test.rs b/crates/shirabe/tests/util/github_test.rs index 019b858..5caf5c7 100644 --- a/crates/shirabe/tests/util/github_test.rs +++ b/crates/shirabe/tests/util/github_test.rs @@ -5,13 +5,13 @@ // available, and a real HttpDownloader reaches curl_multi_init (todo!()). #[test] -#[ignore = "mocks IO/Config/HttpDownloader for the auth flow; a real HttpDownloader reaches curl_multi_init (todo!())"] +#[ignore = "requires getIOMock/getHttpDownloaderMock and getMockBuilder mocks of Config/JsonConfigSource with expects()/willReturn(); no mocking infrastructure exists"] fn test_username_password_authentication_flow() { todo!() } #[test] -#[ignore = "mocks IO/Config/HttpDownloader for the auth flow; a real HttpDownloader reaches curl_multi_init (todo!())"] +#[ignore = "requires getIOMock/getHttpDownloaderMock and getMockBuilder mocks of Config/JsonConfigSource with expects()/willReturn(); no mocking infrastructure exists"] fn test_username_password_failure() { todo!() } diff --git a/crates/shirabe/tests/util/gitlab_test.rs b/crates/shirabe/tests/util/gitlab_test.rs index 27b7258..e7c8bf4 100644 --- a/crates/shirabe/tests/util/gitlab_test.rs +++ b/crates/shirabe/tests/util/gitlab_test.rs @@ -4,14 +4,14 @@ // HttpDownloader to drive the username/password authentication flow. Mocking is not // available, and a real HttpDownloader reaches curl_multi_init (todo!()). +#[ignore = "needs getIOMock/IOMock and getMockBuilder mocks for HttpDownloader/Config/JsonConfigSource (no mock infrastructure)"] #[test] -#[ignore = "mocks IO/Config/HttpDownloader for the auth flow; a real HttpDownloader reaches curl_multi_init (todo!())"] fn test_username_password_authentication_flow() { todo!() } +#[ignore = "needs getIOMock/IOMock and getMockBuilder mocks for HttpDownloader/Config/JsonConfigSource (no mock infrastructure)"] #[test] -#[ignore = "mocks IO/Config/HttpDownloader for the auth flow; a real HttpDownloader reaches curl_multi_init (todo!())"] fn test_username_password_failure() { todo!() } diff --git a/crates/shirabe/tests/util/http/proxy_manager_test.rs b/crates/shirabe/tests/util/http/proxy_manager_test.rs index 0c177f7..dede524 100644 --- a/crates/shirabe/tests/util/http/proxy_manager_test.rs +++ b/crates/shirabe/tests/util/http/proxy_manager_test.rs @@ -38,7 +38,7 @@ impl Drop for TearDown { } #[test] -#[ignore = "not yet ported (ProxyManager is driven by proxy environment variables)"] +#[ignore = "ProxyManager::get_instance returns a shared &'static Mutex, so instance identity (=== / after reset) cannot be expressed through the public API"] fn test_instantiation() { let _tear_down = TearDown; set_up(); @@ -46,49 +46,225 @@ fn test_instantiation() { } #[test] -#[ignore = "not yet ported (ProxyManager is driven by proxy environment variables)"] +#[ignore] fn test_get_proxy_for_request_throws_on_bad_proxy_url() { let _tear_down = TearDown; set_up(); - todo!() + + Platform::put_env("http_proxy", "localhost"); + ProxyManager::reset(); + let mutex = ProxyManager::get_instance(); + let guard = mutex.lock().unwrap(); + let proxy_manager = guard.as_ref().unwrap(); + + assert!( + proxy_manager + .get_proxy_for_request("http://example.com") + .is_err() + ); } #[test] -#[ignore = "not yet ported (ProxyManager is driven by proxy environment variables)"] +#[ignore] fn test_lowercase_overrides_uppercase() { let _tear_down = TearDown; set_up(); - todo!() + + // server, url, expectedUrl + let cases: Vec<(Vec<(&str, &str)>, &str, &str)> = vec![ + ( + vec![ + ("HTTP_PROXY", "http://upper.com"), + ("http_proxy", "http://lower.com"), + ], + "http://repo.org", + "http://lower.com:80", + ), + ( + vec![ + ("CGI_HTTP_PROXY", "http://upper.com"), + ("cgi_http_proxy", "http://lower.com"), + ], + "http://repo.org", + "http://lower.com:80", + ), + ( + vec![ + ("HTTPS_PROXY", "http://upper.com"), + ("https_proxy", "http://lower.com"), + ], + "https://repo.org", + "http://lower.com:80", + ), + ]; + + for (server, url, expected_url) in cases { + set_up(); + for (name, value) in &server { + Platform::put_env(name, value); + } + ProxyManager::reset(); + + let mutex = ProxyManager::get_instance(); + let guard = mutex.lock().unwrap(); + let proxy = guard.as_ref().unwrap().get_proxy_for_request(url).unwrap(); + assert_eq!(expected_url, proxy.get_status(None).unwrap()); + } } #[test] -#[ignore = "not yet ported (ProxyManager is driven by proxy environment variables)"] +#[ignore] fn test_cgi_proxy_is_only_used_when_no_http_proxy() { let _tear_down = TearDown; set_up(); - todo!() + + // server, expectedUrl + let cases: Vec<(Vec<(&str, &str)>, &str)> = vec![ + ( + vec![("CGI_HTTP_PROXY", "http://cgi.com:80")], + "http://cgi.com:80", + ), + ( + vec![ + ("http_proxy", "http://http.com:80"), + ("CGI_HTTP_PROXY", "http://cgi.com:80"), + ], + "http://http.com:80", + ), + ]; + + for (server, expected_url) in cases { + set_up(); + for (name, value) in &server { + Platform::put_env(name, value); + } + ProxyManager::reset(); + + let mutex = ProxyManager::get_instance(); + let guard = mutex.lock().unwrap(); + let proxy = guard + .as_ref() + .unwrap() + .get_proxy_for_request("http://repo.org") + .unwrap(); + assert_eq!(expected_url, proxy.get_status(None).unwrap()); + } } #[test] -#[ignore = "not yet ported (ProxyManager is driven by proxy environment variables)"] +#[ignore] fn test_no_http_proxy_does_not_use_https_proxy() { let _tear_down = TearDown; set_up(); - todo!() + + Platform::put_env("https_proxy", "https://proxy.com:443"); + ProxyManager::reset(); + let mutex = ProxyManager::get_instance(); + let guard = mutex.lock().unwrap(); + let proxy = guard + .as_ref() + .unwrap() + .get_proxy_for_request("http://repo.org") + .unwrap(); + assert_eq!("", proxy.get_status(None).unwrap()); } #[test] -#[ignore = "not yet ported (ProxyManager is driven by proxy environment variables)"] +#[ignore] fn test_no_https_proxy_does_not_use_http_proxy() { let _tear_down = TearDown; set_up(); - todo!() + + Platform::put_env("http_proxy", "http://proxy.com:80"); + ProxyManager::reset(); + let mutex = ProxyManager::get_instance(); + let guard = mutex.lock().unwrap(); + let proxy = guard + .as_ref() + .unwrap() + .get_proxy_for_request("https://repo.org") + .unwrap(); + assert_eq!("", proxy.get_status(None).unwrap()); } #[test] -#[ignore = "not yet ported (ProxyManager is driven by proxy environment variables)"] +#[ignore] fn test_get_proxy_for_request() { + use indexmap::IndexMap; + use shirabe_php_shim::PhpMixed; + let _tear_down = TearDown; set_up(); - todo!() + + let server = vec![ + ("http_proxy", "http://user:p%40ss@proxy.com"), + ("https_proxy", "https://proxy.com:443"), + ("no_proxy", "other.repo.org"), + ]; + + let http_options = + |pairs: &[(&str, PhpMixed)]| -> Option<IndexMap<String, IndexMap<String, PhpMixed>>> { + let mut http: IndexMap<String, PhpMixed> = IndexMap::new(); + for (k, v) in pairs { + http.insert((*k).to_string(), v.clone()); + } + let mut options: IndexMap<String, IndexMap<String, PhpMixed>> = IndexMap::new(); + options.insert("http".to_string(), http); + Some(options) + }; + + // server, url, options, status, excluded + let cases: Vec<( + Vec<(&str, &str)>, + &str, + Option<IndexMap<String, IndexMap<String, PhpMixed>>>, + &str, + bool, + )> = vec![ + (vec![], "http://repo.org", None, "", false), + ( + server.clone(), + "http://repo.org", + http_options(&[ + ("proxy", PhpMixed::String("tcp://proxy.com:80".to_string())), + ( + "header", + PhpMixed::String("Proxy-Authorization: Basic dXNlcjpwQHNz".to_string()), + ), + ("request_fulluri", PhpMixed::Bool(true)), + ]), + "http://***:***@proxy.com:80", + false, + ), + ( + server.clone(), + "https://repo.org", + http_options(&[("proxy", PhpMixed::String("ssl://proxy.com:443".to_string()))]), + "https://proxy.com:443", + false, + ), + ( + server.clone(), + "https://other.repo.org", + None, + "excluded by no_proxy", + true, + ), + ]; + + for (srv, url, options, status, excluded) in cases { + set_up(); + for (name, value) in &srv { + Platform::put_env(name, value); + } + ProxyManager::reset(); + + let mutex = ProxyManager::get_instance(); + let guard = mutex.lock().unwrap(); + let proxy = guard.as_ref().unwrap().get_proxy_for_request(url).unwrap(); + + assert_eq!(options.as_ref(), proxy.get_context_options()); + assert_eq!(status, proxy.get_status(None).unwrap()); + assert_eq!(excluded, proxy.is_excluded_by_no_proxy()); + } } diff --git a/crates/shirabe/tests/util/http_downloader_test.rs b/crates/shirabe/tests/util/http_downloader_test.rs index 238fe83..5c34e19 100644 --- a/crates/shirabe/tests/util/http_downloader_test.rs +++ b/crates/shirabe/tests/util/http_downloader_test.rs @@ -1,13 +1,107 @@ //! ref: composer/tests/Composer/Test/Util/HttpDownloaderTest.php +use std::cell::RefCell; +use std::rc::Rc; + +use indexmap::IndexMap; +use shirabe::io::IOInterface; +use shirabe::io::buffer_io::BufferIO; +use shirabe::util::http_downloader::HttpDownloader; +use shirabe_external_packages::symfony::console::output::output_interface::VERBOSITY_NORMAL; +use shirabe_php_shim::{PHP_EOL, PhpMixed}; + #[test] -#[ignore = "constructs a real HttpDownloader (curl_multi_init is todo!()) and mocks a Config; performs a network request"] +#[ignore = "asserts IOInterface mock ->expects()->method('setAuthentication')->with(...) and performs a live HTTP get; no mock infrastructure exists"] fn test_capture_authentication_params_from_url() { todo!() } #[test] -#[ignore = "constructs a real HttpDownloader (curl_multi_init is todo!()) and mocks a Config; performs a network request"] +#[ignore] fn test_output_warnings() { - todo!() + let io: Rc<RefCell<BufferIO>> = Rc::new(RefCell::new( + BufferIO::new(String::new(), VERBOSITY_NORMAL, None).unwrap(), + )); + let io_dyn: Rc<RefCell<dyn IOInterface>> = io.clone(); + + HttpDownloader::output_warnings(io_dyn.clone(), "$URL", &IndexMap::new()).unwrap(); + assert_eq!("", io.borrow().get_output()); + + let mut data: IndexMap<String, PhpMixed> = IndexMap::new(); + data.insert( + "warning".to_string(), + PhpMixed::String("old warning msg".to_string()), + ); + data.insert( + "warning-versions".to_string(), + PhpMixed::String(">=2.0".to_string()), + ); + data.insert( + "info".to_string(), + PhpMixed::String("old info msg".to_string()), + ); + data.insert( + "info-versions".to_string(), + PhpMixed::String(">=2.0".to_string()), + ); + + let mut warning_should_not = IndexMap::new(); + warning_should_not.insert( + "message".to_string(), + PhpMixed::String("should not appear".to_string()), + ); + warning_should_not.insert("versions".to_string(), PhpMixed::String("<2.2".to_string())); + let mut warning_visible = IndexMap::new(); + warning_visible.insert( + "message".to_string(), + PhpMixed::String("visible warning".to_string()), + ); + warning_visible.insert( + "versions".to_string(), + PhpMixed::String(">=2.2-dev".to_string()), + ); + data.insert( + "warnings".to_string(), + PhpMixed::List(vec![ + PhpMixed::Array(warning_should_not), + PhpMixed::Array(warning_visible), + ]), + ); + + let mut info_should_not = IndexMap::new(); + info_should_not.insert( + "message".to_string(), + PhpMixed::String("should not appear".to_string()), + ); + info_should_not.insert("versions".to_string(), PhpMixed::String("<2.2".to_string())); + let mut info_visible = IndexMap::new(); + info_visible.insert( + "message".to_string(), + PhpMixed::String("visible info".to_string()), + ); + info_visible.insert( + "versions".to_string(), + PhpMixed::String(">=2.2-dev".to_string()), + ); + data.insert( + "infos".to_string(), + PhpMixed::List(vec![ + PhpMixed::Array(info_should_not), + PhpMixed::Array(info_visible), + ]), + ); + + HttpDownloader::output_warnings(io_dyn.clone(), "$URL", &data).unwrap(); + + // the <info> tag are consumed by the OutputFormatter, but not <warning> as that is not a default output format + assert_eq!( + format!( + "<warning>Warning from $URL: old warning msg</warning>{eol}\ + Info from $URL: old info msg{eol}\ + <warning>Warning from $URL: visible warning</warning>{eol}\ + Info from $URL: visible info{eol}", + eol = PHP_EOL + ), + io.borrow().get_output() + ); } diff --git a/crates/shirabe/tests/util/metadata_minifier_test.rs b/crates/shirabe/tests/util/metadata_minifier_test.rs index 03bee8e..85ed507 100644 --- a/crates/shirabe/tests/util/metadata_minifier_test.rs +++ b/crates/shirabe/tests/util/metadata_minifier_test.rs @@ -1,7 +1,7 @@ //! ref: composer/tests/Composer/Test/Util/MetadataMinifierTest.php #[test] -#[ignore = "MetadataMinifier::minify is intentionally not ported in shirabe_external_packages (unused by Composer itself)"] +#[ignore = "MetadataMinifier::minify is not ported (intentionally absent in metadata_minifier.rs)"] fn test_minify_expand() { todo!() } diff --git a/crates/shirabe/tests/util/perforce_test.rs b/crates/shirabe/tests/util/perforce_test.rs index fb517ba..0a62ebb 100644 --- a/crates/shirabe/tests/util/perforce_test.rs +++ b/crates/shirabe/tests/util/perforce_test.rs @@ -3,6 +3,55 @@ // These mock IO and a ProcessExecutor to drive Perforce client/stream/command behaviour; // mocking is not available here. +use std::cell::RefCell; +use std::rc::Rc; + +use indexmap::IndexMap; +use shirabe::io::{IOInterface, NullIO}; +use shirabe::util::{Perforce, ProcessExecutor}; +use shirabe_php_shim::PhpMixed; + +const TEST_DEPOT: &str = "depot"; +const TEST_BRANCH: &str = "branch"; +const TEST_P4USER: &str = "user"; +const TEST_CLIENT_NAME: &str = "TEST"; +const TEST_PORT: &str = "port"; +const TEST_PATH: &str = "path"; + +fn get_test_repo_config() -> IndexMap<String, PhpMixed> { + let mut config = IndexMap::new(); + config.insert( + "depot".to_string(), + PhpMixed::String(TEST_DEPOT.to_string()), + ); + config.insert( + "branch".to_string(), + PhpMixed::String(TEST_BRANCH.to_string()), + ); + config.insert( + "p4user".to_string(), + PhpMixed::String(TEST_P4USER.to_string()), + ); + config.insert( + "unique_perforce_client_name".to_string(), + PhpMixed::String(TEST_CLIENT_NAME.to_string()), + ); + config +} + +fn create_new_perforce_with_windows_flag(flag: bool) -> Perforce { + let process = Rc::new(RefCell::new(ProcessExecutor::new(None))); + let io: Rc<RefCell<dyn IOInterface>> = Rc::new(RefCell::new(NullIO::new())); + Perforce::new( + get_test_repo_config(), + TEST_PORT.to_string(), + TEST_PATH.to_string(), + process, + flag, + io, + ) +} + #[allow(dead_code)] fn set_up() { // Builds mocked ProcessExecutor/IO, the test repo config, and a Windows-flagged Perforce; @@ -10,230 +59,296 @@ fn set_up() { todo!() } +#[ignore] #[test] -#[ignore = "mocks IO/ProcessExecutor to drive Perforce; mocking is not available"] fn test_get_client_without_stream() { - todo!() + let mut perforce = create_new_perforce_with_windows_flag(true); + + let client = perforce.get_client(); + + let expected = "composer_perforce_TEST_depot"; + assert_eq!(expected, client); } +#[ignore] #[test] -#[ignore = "mocks IO/ProcessExecutor to drive Perforce; mocking is not available"] fn test_get_client_from_stream() { - todo!() + let mut perforce = create_new_perforce_with_windows_flag(true); + perforce.set_stream("//depot/branch"); + + let client = perforce.get_client(); + + let expected = "composer_perforce_TEST_depot_branch"; + assert_eq!(expected, client); } +#[ignore] #[test] -#[ignore = "mocks IO/ProcessExecutor to drive Perforce; mocking is not available"] fn test_get_stream_without_stream() { - todo!() + let mut perforce = create_new_perforce_with_windows_flag(true); + + let stream = perforce.get_stream(); + assert_eq!("//depot", stream); } +#[ignore] #[test] -#[ignore = "mocks IO/ProcessExecutor to drive Perforce; mocking is not available"] fn test_get_stream_with_stream() { - todo!() + let mut perforce = create_new_perforce_with_windows_flag(true); + perforce.set_stream("//depot/branch"); + + let stream = perforce.get_stream(); + assert_eq!("//depot/branch", stream); } +#[ignore] #[test] -#[ignore = "mocks IO/ProcessExecutor to drive Perforce; mocking is not available"] fn test_get_stream_without_label_with_stream_without_label() { - todo!() + let perforce = create_new_perforce_with_windows_flag(true); + + let stream = perforce.get_stream_without_label("//depot/branch"); + assert_eq!("//depot/branch", stream); } +#[ignore] #[test] -#[ignore = "mocks IO/ProcessExecutor to drive Perforce; mocking is not available"] fn test_get_stream_without_label_with_stream_with_label() { - todo!() + let perforce = create_new_perforce_with_windows_flag(true); + + let stream = perforce.get_stream_without_label("//depot/branching@label"); + assert_eq!("//depot/branching", stream); } +#[ignore] #[test] -#[ignore = "mocks IO/ProcessExecutor to drive Perforce; mocking is not available"] fn test_get_client_spec() { - todo!() + let mut perforce = create_new_perforce_with_windows_flag(true); + + let client_spec = perforce.get_p4_client_spec(); + let expected = "path/composer_perforce_TEST_depot.p4.spec"; + assert_eq!(expected, client_spec); } +#[ignore] #[test] -#[ignore = "mocks IO/ProcessExecutor to drive Perforce; mocking is not available"] fn test_generate_p4_command() { - todo!() + let mut perforce = create_new_perforce_with_windows_flag(true); + + let p4_command = + perforce.generate_p4_command(vec!["do".to_string(), "something".to_string()], true); + let expected = vec![ + "p4".to_string(), + "-u".to_string(), + "user".to_string(), + "-c".to_string(), + "composer_perforce_TEST_depot".to_string(), + "-p".to_string(), + "port".to_string(), + "do".to_string(), + "something".to_string(), + ]; + assert_eq!(expected, p4_command); } +#[ignore] #[test] -#[ignore = "mocks IO/ProcessExecutor to drive Perforce; mocking is not available"] fn test_query_p4_user_with_user_already_set() { - todo!() + let mut perforce = create_new_perforce_with_windows_flag(true); + + perforce.query_p4_user(); + assert_eq!(Some(TEST_P4USER.to_string()), perforce.get_user()); } #[test] -#[ignore = "mocks IO/ProcessExecutor to drive Perforce; mocking is not available"] +#[ignore = "requires getProcessExecutorMock with expects() command stdout stubbing (p4 set => P4USER=...); no mocking infrastructure exists"] fn test_query_p4_user_with_user_set_in_p4_variables_with_windows_os() { todo!() } #[test] -#[ignore = "mocks IO/ProcessExecutor to drive Perforce; mocking is not available"] +#[ignore = "requires getProcessExecutorMock with expects() command stdout stubbing (echo $P4USER => ...); no mocking infrastructure exists"] fn test_query_p4_user_with_user_set_in_p4_variables_not_windows_os() { todo!() } #[test] -#[ignore = "mocks IO/ProcessExecutor to drive Perforce; mocking is not available"] +#[ignore = "requires mocked IOInterface ask()->willReturn and getProcessExecutorMock; no mocking infrastructure exists"] fn test_query_p4_user_queries_for_user() { todo!() } #[test] -#[ignore = "mocks IO/ProcessExecutor to drive Perforce; mocking is not available"] +#[ignore = "requires mocked IOInterface ask()->willReturn and getProcessExecutorMock expects() command verification; no mocking infrastructure exists"] fn test_query_p4_user_stores_response_to_query_for_user_with_windows() { todo!() } #[test] -#[ignore = "mocks IO/ProcessExecutor to drive Perforce; mocking is not available"] +#[ignore = "requires mocked IOInterface ask()->willReturn and getProcessExecutorMock expects() command verification; no mocking infrastructure exists"] fn test_query_p4_user_stores_response_to_query_for_user_without_windows() { todo!() } #[test] -#[ignore = "mocks IO/ProcessExecutor to drive Perforce; mocking is not available"] +#[ignore = "requires mocked IOInterface ask()->willReturn and getProcessExecutorMock expects() command verification; no mocking infrastructure exists"] fn test_query_p4_user_escapes_injection_on_windows() { todo!() } #[test] -#[ignore = "mocks IO/ProcessExecutor to drive Perforce; mocking is not available"] +#[ignore = "requires mocked IOInterface ask()->willReturn and getProcessExecutorMock expects() command verification; no mocking infrastructure exists"] fn test_query_p4_user_escapes_injection_on_unix() { todo!() } +#[ignore] #[test] -#[ignore = "mocks IO/ProcessExecutor to drive Perforce; mocking is not available"] fn test_query_p4_password_with_password_already_set() { - todo!() + let mut repo_config = IndexMap::new(); + repo_config.insert("depot".to_string(), PhpMixed::String("depot".to_string())); + repo_config.insert("branch".to_string(), PhpMixed::String("branch".to_string())); + repo_config.insert("p4user".to_string(), PhpMixed::String("user".to_string())); + repo_config.insert( + "p4password".to_string(), + PhpMixed::String("TEST_PASSWORD".to_string()), + ); + let process = Rc::new(RefCell::new(ProcessExecutor::new(None))); + let io: Rc<RefCell<dyn IOInterface>> = Rc::new(RefCell::new(NullIO::new())); + let mut perforce = Perforce::new( + repo_config, + "port".to_string(), + "path".to_string(), + process, + false, + io, + ); + + let password = perforce.query_p4_password(); + assert_eq!(Some("TEST_PASSWORD".to_string()), password); } #[test] -#[ignore = "mocks IO/ProcessExecutor to drive Perforce; mocking is not available"] +#[ignore = "requires getProcessExecutorMock with expects() command stdout stubbing (p4 set => P4PASSWD=...); no mocking infrastructure exists"] fn test_query_p4_password_with_password_set_in_p4_variables_with_windows_os() { todo!() } #[test] -#[ignore = "mocks IO/ProcessExecutor to drive Perforce; mocking is not available"] +#[ignore = "requires getProcessExecutorMock with expects() command stdout stubbing (echo $P4PASSWD => ...); no mocking infrastructure exists"] fn test_query_p4_password_with_password_set_in_p4_variables_not_windows_os() { todo!() } #[test] -#[ignore = "mocks IO/ProcessExecutor to drive Perforce; mocking is not available"] +#[ignore = "requires mocked IOInterface askAndHideAnswer()->willReturn; no mocking infrastructure exists"] fn test_query_p4_password_queries_for_password() { todo!() } #[test] -#[ignore = "mocks IO/ProcessExecutor to drive Perforce; mocking is not available"] +#[ignore = "requires getProcessExecutorMock expects() command verification for the p4 client spec write; no mocking infrastructure exists"] fn test_write_p4_client_spec_without_stream() { todo!() } #[test] -#[ignore = "mocks IO/ProcessExecutor to drive Perforce; mocking is not available"] +#[ignore = "requires getProcessExecutorMock expects() command verification for the p4 client spec write; no mocking infrastructure exists"] fn test_write_p4_client_spec_with_stream() { todo!() } #[test] -#[ignore = "mocks IO/ProcessExecutor to drive Perforce; mocking is not available"] +#[ignore = "requires getProcessExecutorMock expects() command stdout stubbing for p4 login -s; no mocking infrastructure exists"] fn test_is_logged_in() { todo!() } #[test] -#[ignore = "mocks IO/ProcessExecutor to drive Perforce; mocking is not available"] +#[ignore = "requires getProcessExecutorMock expects() command stdout stubbing for p4 streams; no mocking infrastructure exists"] fn test_get_branches_with_stream() { todo!() } #[test] -#[ignore = "mocks IO/ProcessExecutor to drive Perforce; mocking is not available"] +#[ignore = "requires getProcessExecutorMock expects() command stdout stubbing for p4 changes; no mocking infrastructure exists"] fn test_get_branches_without_stream() { todo!() } #[test] -#[ignore = "mocks IO/ProcessExecutor to drive Perforce; mocking is not available"] +#[ignore = "requires getProcessExecutorMock expects() command stdout stubbing for p4 changes; no mocking infrastructure exists"] fn test_get_tags_without_stream() { todo!() } #[test] -#[ignore = "mocks IO/ProcessExecutor to drive Perforce; mocking is not available"] +#[ignore = "requires getProcessExecutorMock expects() command stdout stubbing for p4 changes; no mocking infrastructure exists"] fn test_get_tags_with_stream() { todo!() } #[test] -#[ignore = "mocks IO/ProcessExecutor to drive Perforce; mocking is not available"] +#[ignore = "requires getProcessExecutorMock expects() command stdout stubbing for p4 streams; no mocking infrastructure exists"] fn test_check_stream_without_stream() { todo!() } #[test] -#[ignore = "mocks IO/ProcessExecutor to drive Perforce; mocking is not available"] +#[ignore = "requires getProcessExecutorMock expects() command stdout stubbing for p4 streams; no mocking infrastructure exists"] fn test_check_stream_with_stream() { todo!() } #[test] -#[ignore = "mocks IO/ProcessExecutor to drive Perforce; mocking is not available"] +#[ignore = "requires getProcessExecutorMock expects() command stdout stubbing for p4 print composer.json; no mocking infrastructure exists"] fn test_get_composer_information_without_label_without_stream() { todo!() } #[test] -#[ignore = "mocks IO/ProcessExecutor to drive Perforce; mocking is not available"] +#[ignore = "requires getProcessExecutorMock expects() command stdout stubbing for p4 print composer.json; no mocking infrastructure exists"] fn test_get_composer_information_with_label_without_stream() { todo!() } #[test] -#[ignore = "mocks IO/ProcessExecutor to drive Perforce; mocking is not available"] +#[ignore = "requires getProcessExecutorMock expects() command stdout stubbing for p4 print composer.json; no mocking infrastructure exists"] fn test_get_composer_information_without_label_with_stream() { todo!() } #[test] -#[ignore = "mocks IO/ProcessExecutor to drive Perforce; mocking is not available"] +#[ignore = "requires getProcessExecutorMock expects() command stdout stubbing for p4 print composer.json; no mocking infrastructure exists"] fn test_get_composer_information_with_label_with_stream() { todo!() } #[test] -#[ignore = "mocks IO/ProcessExecutor to drive Perforce; mocking is not available"] +#[ignore = "requires getProcessExecutorMock expects() command verification for p4 sync; no mocking infrastructure exists"] fn test_sync_code_base_without_stream() { todo!() } #[test] -#[ignore = "mocks IO/ProcessExecutor to drive Perforce; mocking is not available"] +#[ignore = "requires getProcessExecutorMock expects() command verification for p4 sync; no mocking infrastructure exists"] fn test_sync_code_base_with_stream() { todo!() } #[test] -#[ignore = "mocks IO/ProcessExecutor to drive Perforce; mocking is not available"] +#[ignore = "requires getProcessExecutorMock expects() command stdout stubbing for p4 info -s; no mocking infrastructure exists"] fn test_check_server_exists() { todo!() } #[test] -#[ignore = "mocks IO/ProcessExecutor to drive Perforce; mocking is not available"] +#[ignore = "requires getProcessExecutorMock expects() command stdout stubbing for p4 info -s; no mocking infrastructure exists"] fn test_check_server_client_error() { todo!() } #[test] -#[ignore = "mocks IO/ProcessExecutor to drive Perforce; mocking is not available"] +#[ignore = "requires getProcessExecutorMock expects() command verification for p4 client -d; no mocking infrastructure exists"] fn test_cleanup_client_spec_should_delete_client() { todo!() } diff --git a/crates/shirabe/tests/util/process_executor_test.rs b/crates/shirabe/tests/util/process_executor_test.rs index 88449f1..f17f162 100644 --- a/crates/shirabe/tests/util/process_executor_test.rs +++ b/crates/shirabe/tests/util/process_executor_test.rs @@ -3,68 +3,206 @@ // These run real subprocesses (capturing output/stderr/timeout) and assert ProcessExecutor's // password hiding, line splitting and argument escaping; the subprocess execution and mocked // IO are not ported. + +use std::cell::RefCell; +use std::rc::Rc; + +use shirabe::io::ConsoleIO; +use shirabe::io::IOInterface; +use shirabe::io::buffer_io::BufferIO; +use shirabe::util::process_executor::ProcessExecutor; +use shirabe_external_packages::symfony::console::helper::HelperSet; +use shirabe_external_packages::symfony::console::input::array_input::ArrayInput; +use shirabe_external_packages::symfony::console::input::input_interface::InputInterface; +use shirabe_external_packages::symfony::console::output::buffered_output::BufferedOutput; +use shirabe_external_packages::symfony::console::output::output_interface::{ + OutputInterface, VERBOSITY_DEBUG, VERBOSITY_NORMAL, +}; +use shirabe_php_shim::{PHP_EOL, trim}; + +#[ignore] #[test] -#[ignore = "not yet ported (runs subprocesses / mocks IO; argument-escaping and split helpers included)"] fn test_execute_captures_output() { - todo!() + let mut process = ProcessExecutor::new(None); + let mut output = String::new(); + process.execute("echo foo", &mut output, ()).unwrap(); + assert_eq!(format!("foo{}", PHP_EOL), output); } +#[ignore = "requires PHP output buffering (ob_start/ob_get_clean) to capture stdout; no equivalent symbol"] #[test] -#[ignore = "not yet ported (runs subprocesses / mocks IO; argument-escaping and split helpers included)"] fn test_execute_outputs_if_not_captured() { todo!() } +#[ignore = "requires getMockBuilder('IOInterface') with expects()->once()->method('writeRaw')->with() expectation verification; no mocking framework"] #[test] -#[ignore = "not yet ported (runs subprocesses / mocks IO; argument-escaping and split helpers included)"] fn test_use_io_is_not_null_and_if_not_captured() { todo!() } +#[ignore] #[test] -#[ignore = "not yet ported (runs subprocesses / mocks IO; argument-escaping and split helpers included)"] fn test_execute_captures_stderr() { - todo!() + let mut process = ProcessExecutor::new(None); + let mut output = String::new(); + process.execute("cat foo", &mut output, ()).unwrap(); + assert!( + process + .get_error_output() + .contains("foo: No such file or directory") + ); } +#[ignore] #[test] -#[ignore = "not yet ported (runs subprocesses / mocks IO; argument-escaping and split helpers included)"] fn test_timeout() { - todo!() + ProcessExecutor::set_timeout(1_i64); + let process = ProcessExecutor::new(None); + assert_eq!(1, ProcessExecutor::get_timeout()); + let _ = &process; + ProcessExecutor::set_timeout(60_i64); +} + +fn hide_password_provider() -> Vec<(&'static str, &'static str)> { + vec![ + ( + "echo https://foo:bar@example.org/", + "echo https://foo:***@example.org/", + ), + ("echo http://foo@example.org", "echo http://foo@example.org"), + ( + "echo http://abcdef1234567890234578:x-oauth-token@github.com/", + "echo http://***:***@github.com/", + ), + ( + "echo http://github_pat_1234567890abcdefghijkl_1234567890abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVW:x-oauth-token@github.com/", + "echo http://***:***@github.com/", + ), + ( + "svn ls --verbose --non-interactive --username 'foo' --password 'bar' 'https://foo.example.org/svn/'", + "svn ls --verbose --non-interactive --username 'foo' --password '***' 'https://foo.example.org/svn/'", + ), + ( + "svn ls --verbose --non-interactive --username 'foo' --password 'bar 'bar' 'https://foo.example.org/svn/'", + "svn ls --verbose --non-interactive --username 'foo' --password '***' 'https://foo.example.org/svn/'", + ), + ] } +#[ignore] #[test] -#[ignore = "not yet ported (runs subprocesses / mocks IO; argument-escaping and split helpers included)"] fn test_hide_passwords() { - todo!() + for (command, expected_command_output) in hide_password_provider() { + let buffer = Rc::new(RefCell::new( + BufferIO::new(String::new(), VERBOSITY_DEBUG, None).unwrap(), + )); + let mut process = + ProcessExecutor::new(Some(buffer.clone() as Rc<RefCell<dyn IOInterface>>)); + let mut output = String::new(); + process.execute(command, &mut output, ()).unwrap(); + assert_eq!( + format!("Executing command (CWD): {}", expected_command_output), + trim(&buffer.borrow().get_output(), None) + ); + } } +#[ignore] #[test] -#[ignore = "not yet ported (runs subprocesses / mocks IO; argument-escaping and split helpers included)"] fn test_doesnt_hide_ports() { - todo!() + let buffer = Rc::new(RefCell::new( + BufferIO::new(String::new(), VERBOSITY_DEBUG, None).unwrap(), + )); + let mut process = ProcessExecutor::new(Some(buffer.clone() as Rc<RefCell<dyn IOInterface>>)); + let mut output = String::new(); + process + .execute("echo https://localhost:1234/", &mut output, ()) + .unwrap(); + assert_eq!( + "Executing command (CWD): echo https://localhost:1234/", + trim(&buffer.borrow().get_output(), None) + ); } +#[ignore = "splitLines is called with null in the PHP test, but split_lines accepts only &str (no ?string/Option overload)"] #[test] -#[ignore = "not yet ported (runs subprocesses / mocks IO; argument-escaping and split helpers included)"] fn test_split_lines() { todo!() } +#[ignore] #[test] -#[ignore = "not yet ported (runs subprocesses / mocks IO; argument-escaping and split helpers included)"] fn test_console_io_does_not_format_symfony_console_style() { - todo!() + let output = Rc::new(RefCell::new(BufferedOutput::new( + Some(VERBOSITY_NORMAL), + true, + None, + ))); + let input: Rc<RefCell<dyn InputInterface>> = + Rc::new(RefCell::new(ArrayInput::new(vec![], None).unwrap())); + let console_io = ConsoleIO::new( + input, + output.clone() as Rc<RefCell<dyn OutputInterface>>, + HelperSet::default(), + ); + let mut process = ProcessExecutor::new(Some( + Rc::new(RefCell::new(console_io)) as Rc<RefCell<dyn IOInterface>> + )); + + process + .execute( + r#"php -ddisplay_errors=0 -derror_reporting=0 -r "echo '<error>foo</error>'.PHP_EOL;""#, + (), + (), + ) + .unwrap(); + assert_eq!( + format!("<error>foo</error>{}", PHP_EOL), + output.borrow().fetch() + ); } +#[ignore = "executeAsync returns a Process, not a cancelable promise; no promise/cancel symbol exists"] #[test] -#[ignore = "not yet ported (runs subprocesses / mocks IO; argument-escaping and split helpers included)"] fn test_execute_async_cancel() { todo!() } +fn data_escape_arguments() -> Vec<(&'static str, &'static str)> { + // (argument, unix-expected). Not on Windows, so the unix column is used. + // null and false arguments are coerced to the empty string. + vec![ + ("", "''"), + ("", "''"), + ("", "''"), + ("a'bc", "'a'\\''bc'"), + ("a\nb\nc", "'a\nb\nc'"), + ("a b c", "'a b c'"), + ("a\tb\tc", "'a\tb\tc'"), + ("abc", "'abc'"), + ("a,bc", "'a,bc'"), + ("a\"bc", "'a\"bc'"), + ("a\\\"bc", "'a\\\"bc'"), + ("ab\\\\c\\", "'ab\\\\c\\'"), + ("a b c\\\\", "'a b c\\\\'"), + ("a \"b\" c", "'a \"b\" c'"), + ("%path%", "'%path%'"), + ("%path", "'%path'"), + ("%%path", "'%%path'"), + ("!path!", "'!path!'"), + ("!path", "'!path'"), + ("!!path", "'!!path'"), + ("<>\"&|()^", "'<>\"&|()^'"), + ("<> &| ()^", "'<> &| ()^'"), + ("<>&|()^", "'<>&|()^'"), + ] +} + +#[ignore] #[test] -#[ignore = "not yet ported (runs subprocesses / mocks IO; argument-escaping and split helpers included)"] fn test_escape_argument() { - todo!() + for (argument, unix) in data_escape_arguments() { + assert_eq!(unix, ProcessExecutor::escape(argument)); + } } diff --git a/crates/shirabe/tests/util/remote_filesystem_test.rs b/crates/shirabe/tests/util/remote_filesystem_test.rs index 1fc9f7e..b39a2dd 100644 --- a/crates/shirabe/tests/util/remote_filesystem_test.rs +++ b/crates/shirabe/tests/util/remote_filesystem_test.rs @@ -4,79 +4,79 @@ // building and downloads; mocking/reflection are not available and a real HttpDownloader // reaches curl_multi_init (todo!()). #[test] -#[ignore = "mocks IO/Config/HttpDownloader (curl_multi_init todo!()) and uses reflection; not ported"] +#[ignore = "requires a mocked IOInterface (expects(once())->hasAuthentication) and ReflectionMethod to invoke private get_options_for_url plus ReflectionProperty on private file_url; no mocking/reflection infrastructure exists"] fn test_get_options_for_url() { todo!() } #[test] -#[ignore = "mocks IO/Config/HttpDownloader (curl_multi_init todo!()) and uses reflection; not ported"] +#[ignore = "requires a mocked IOInterface (expects(once())->hasAuthentication/getAuthentication) and ReflectionMethod to invoke private get_options_for_url plus ReflectionProperty on private file_url; no mocking/reflection infrastructure exists"] fn test_get_options_for_url_with_authorization() { todo!() } #[test] -#[ignore = "mocks IO/Config/HttpDownloader (curl_multi_init todo!()) and uses reflection; not ported"] +#[ignore = "requires a mocked IOInterface (expects(once())->hasAuthentication/getAuthentication) and ReflectionMethod to invoke private get_options_for_url plus ReflectionProperty on private file_url; no mocking/reflection infrastructure exists"] fn test_get_options_for_url_with_stream_options() { todo!() } #[test] -#[ignore = "mocks IO/Config/HttpDownloader (curl_multi_init todo!()) and uses reflection; not ported"] +#[ignore = "requires a mocked IOInterface (expects(once())->hasAuthentication/getAuthentication) and ReflectionMethod to invoke private get_options_for_url plus ReflectionProperty on private file_url; no mocking/reflection infrastructure exists"] fn test_get_options_for_url_with_call_options_keeps_header() { todo!() } #[test] -#[ignore = "mocks IO/Config/HttpDownloader (curl_multi_init todo!()) and uses reflection; not ported"] +#[ignore = "requires a mocked IOInterface and ReflectionMethod to invoke private callback_get plus ReflectionProperty to read private bytes_max; no mocking/reflection infrastructure exists"] fn test_callback_get_file_size() { todo!() } #[test] -#[ignore = "mocks IO/Config/HttpDownloader (curl_multi_init todo!()) and uses reflection; not ported"] +#[ignore = "requires a mocked IOInterface (expects(once())->overwriteError) and ReflectionProperty to set private bytes_max/progress and read private last_progress; no mocking/reflection infrastructure exists"] fn test_callback_get_notify_progress() { todo!() } #[test] -#[ignore = "mocks IO/Config/HttpDownloader (curl_multi_init todo!()) and uses reflection; not ported"] +#[ignore = "requires a mocked IOInterface and ReflectionMethod to invoke private callback_get; no mocking/reflection infrastructure exists"] fn test_callback_get_passes_through404() { todo!() } #[test] -#[ignore = "mocks IO/Config/HttpDownloader (curl_multi_init todo!()) and uses reflection; not ported"] +#[ignore = "requires a mocked IOInterface and a real get_contents which bottoms at curl_multi_init (todo!()); no mocking infrastructure exists"] fn test_get_contents() { todo!() } #[test] -#[ignore = "mocks IO/Config/HttpDownloader (curl_multi_init todo!()) and uses reflection; not ported"] +#[ignore = "requires a mocked IOInterface and a real copy which bottoms at curl_multi_init (todo!()); no mocking infrastructure exists"] fn test_copy() { todo!() } #[test] -#[ignore = "mocks IO/Config/HttpDownloader (curl_multi_init todo!()) and uses reflection; not ported"] +#[ignore = "requires a MockObject subclass of RemoteFilesystem overriding private get_remote_contents; no mocking infrastructure exists"] fn test_copy_with_no_retry_on_failure() { todo!() } #[test] -#[ignore = "mocks IO/Config/HttpDownloader (curl_multi_init todo!()) and uses reflection; not ported"] +#[ignore = "requires MockObject subclasses overriding RemoteFilesystem::get_remote_contents and AuthHelper::prompt_auth_if_needed; no mocking infrastructure exists"] fn test_copy_with_success_on_retry() { todo!() } #[test] -#[ignore = "mocks IO/Config/HttpDownloader (curl_multi_init todo!()) and uses reflection; not ported"] +#[ignore = "requires a mocked IOInterface and ReflectionMethod to invoke private get_options_for_url plus ReflectionProperty on private file_url; no mocking/reflection infrastructure exists"] fn test_get_options_for_url_creates_secure_tls_defaults() { todo!() } #[test] -#[ignore = "mocks IO/Config/HttpDownloader (curl_multi_init todo!()) and uses reflection; not ported"] +#[ignore = "requires a mocked ConsoleIO (getMockBuilder disableOriginalConstructor) and a real getContents network download reaching curl_multi_init (todo!()); no mocking infrastructure exists"] fn test_bit_bucket_public_download() { todo!() } diff --git a/crates/shirabe/tests/util/stream_context_factory_test.rs b/crates/shirabe/tests/util/stream_context_factory_test.rs index f58ee75..23d4392 100644 --- a/crates/shirabe/tests/util/stream_context_factory_test.rs +++ b/crates/shirabe/tests/util/stream_context_factory_test.rs @@ -3,8 +3,11 @@ // These build a stream context and assert proxy/option handling driven by HTTP(S)_PROXY / // no_proxy environment variables; the env-dependent setup (without its setUp/tearDown // isolation) is not ported. +use indexmap::IndexMap; use shirabe::util::http::proxy_manager::ProxyManager; use shirabe::util::platform::Platform; +use shirabe::util::stream_context_factory::StreamContextFactory; +use shirabe_php_shim::{PhpMixed, implode, stripos}; fn set_up() { Platform::clear_env("HTTP_PROXY"); @@ -35,7 +38,7 @@ impl Drop for TearDown { } #[test] -#[ignore = "not yet ported (StreamContextFactory proxy/option building is driven by proxy env vars)"] +#[ignore = "stream_context_get_options/stream_context_get_params shim functions do not exist; cannot read back created context to assert"] fn test_get_context() { let _tear_down = TearDown; set_up(); @@ -43,7 +46,7 @@ fn test_get_context() { } #[test] -#[ignore = "not yet ported (StreamContextFactory proxy/option building is driven by proxy env vars)"] +#[ignore = "stream_context_get_options shim function does not exist; cannot read back created context to assert"] fn test_http_proxy() { let _tear_down = TearDown; set_up(); @@ -51,7 +54,7 @@ fn test_http_proxy() { } #[test] -#[ignore = "not yet ported (StreamContextFactory proxy/option building is driven by proxy env vars)"] +#[ignore = "stream_context_get_options shim function does not exist; cannot read back created context to assert"] fn test_http_proxy_with_no_proxy() { let _tear_down = TearDown; set_up(); @@ -59,7 +62,7 @@ fn test_http_proxy_with_no_proxy() { } #[test] -#[ignore = "not yet ported (StreamContextFactory proxy/option building is driven by proxy env vars)"] +#[ignore = "stream_context_get_options shim function does not exist; cannot read back created context to assert"] fn test_http_proxy_with_no_proxy_wildcard() { let _tear_down = TearDown; set_up(); @@ -67,7 +70,7 @@ fn test_http_proxy_with_no_proxy_wildcard() { } #[test] -#[ignore = "not yet ported (StreamContextFactory proxy/option building is driven by proxy env vars)"] +#[ignore = "stream_context_get_options shim function does not exist; cannot read back created context to assert"] fn test_options_are_preserved() { let _tear_down = TearDown; set_up(); @@ -75,7 +78,7 @@ fn test_options_are_preserved() { } #[test] -#[ignore = "not yet ported (StreamContextFactory proxy/option building is driven by proxy env vars)"] +#[ignore = "stream_context_get_options shim function does not exist; cannot read back created context to assert"] fn test_http_proxy_without_port() { let _tear_down = TearDown; set_up(); @@ -83,7 +86,7 @@ fn test_http_proxy_without_port() { } #[test] -#[ignore = "not yet ported (StreamContextFactory proxy/option building is driven by proxy env vars)"] +#[ignore = "stream_context_get_options shim function does not exist; cannot read back created context to assert"] fn test_https_proxy_override() { let _tear_down = TearDown; set_up(); @@ -91,7 +94,7 @@ fn test_https_proxy_override() { } #[test] -#[ignore = "not yet ported (StreamContextFactory proxy/option building is driven by proxy env vars)"] +#[ignore = "stream_context_get_options shim function does not exist; cannot read back created context to assert"] fn test_ssl_proxy() { let _tear_down = TearDown; set_up(); @@ -99,7 +102,7 @@ fn test_ssl_proxy() { } #[test] -#[ignore = "not yet ported (StreamContextFactory proxy/option building is driven by proxy env vars)"] +#[ignore = "stream_context_get_options shim function does not exist; cannot read back created context to assert"] fn test_ensure_thatfix_http_header_field_moves_content_type_to_end_of_options() { let _tear_down = TearDown; set_up(); @@ -107,17 +110,54 @@ fn test_ensure_thatfix_http_header_field_moves_content_type_to_end_of_options() } #[test] -#[ignore = "not yet ported (StreamContextFactory proxy/option building is driven by proxy env vars)"] +#[ignore] fn test_init_options_does_include_proxy_auth_headers() { let _tear_down = TearDown; set_up(); - todo!() + Platform::put_env( + "https_proxy", + "http://username:password@proxyserver.net:3128/", + ); + + let options: IndexMap<String, PhpMixed> = IndexMap::new(); + let options = + StreamContextFactory::init_options("https://example.org", options, false).unwrap(); + let header_list: Vec<String> = options + .get("http") + .and_then(|v| v.as_array()) + .and_then(|a| a.get("header")) + .and_then(|v| v.as_list()) + .unwrap() + .iter() + .filter_map(|item| item.as_string().map(|s| s.to_string())) + .collect(); + let headers = implode(" ", &header_list); + + assert!(stripos(&headers, "Proxy-Authorization").is_some()); } #[test] -#[ignore = "not yet ported (StreamContextFactory proxy/option building is driven by proxy env vars)"] +#[ignore] fn test_init_options_for_curl_does_not_include_proxy_auth_headers() { let _tear_down = TearDown; set_up(); - todo!() + Platform::put_env( + "http_proxy", + "http://username:password@proxyserver.net:3128/", + ); + + let options: IndexMap<String, PhpMixed> = IndexMap::new(); + let options = StreamContextFactory::init_options("https://example.org", options, true).unwrap(); + let header_list: Vec<String> = options + .get("http") + .and_then(|v| v.as_array()) + .and_then(|a| a.get("header")) + .and_then(|v| v.as_list()) + .unwrap() + .iter() + .filter_map(|item| item.as_string().map(|s| s.to_string())) + .collect(); + let headers = implode(" ", &header_list); + + assert!(stripos(&headers, "Proxy-Authorization").is_none()); } diff --git a/crates/shirabe/tests/util/svn_test.rs b/crates/shirabe/tests/util/svn_test.rs index 5e2775f..90a6b45 100644 --- a/crates/shirabe/tests/util/svn_test.rs +++ b/crates/shirabe/tests/util/svn_test.rs @@ -1,33 +1,31 @@ //! ref: composer/tests/Composer/Test/Util/SvnTest.php -// These mock IO/Config and use reflection to drive Svn's credential handling; mocking and -// reflection are not available here. +#[ignore = "Svn::get_credential_args is pub(crate) (crate-private); unreachable from an integration test"] #[test] -#[ignore = "mocks IO/Config and uses reflection to drive Svn credentials; not ported"] fn test_credentials() { todo!() } +#[ignore = "Svn::get_command is pub(crate) (crate-private); unreachable from an integration test"] #[test] -#[ignore = "mocks IO/Config and uses reflection to drive Svn credentials; not ported"] fn test_interactive_string() { todo!() } +#[ignore = "Svn::get_credential_args is pub(crate) (crate-private); unreachable from an integration test"] #[test] -#[ignore = "mocks IO/Config and uses reflection to drive Svn credentials; not ported"] fn test_credentials_from_config() { todo!() } +#[ignore = "Svn::get_credential_args is pub(crate) (crate-private); unreachable from an integration test"] #[test] -#[ignore = "mocks IO/Config and uses reflection to drive Svn credentials; not ported"] fn test_credentials_from_config_with_cache_credentials_true() { todo!() } +#[ignore = "Svn::get_credential_args is pub(crate) (crate-private); unreachable from an integration test"] #[test] -#[ignore = "mocks IO/Config and uses reflection to drive Svn credentials; not ported"] fn test_credentials_from_config_with_cache_credentials_false() { todo!() } diff --git a/crates/shirabe/tests/util/tls_helper_test.rs b/crates/shirabe/tests/util/tls_helper_test.rs index 260ff47..4717282 100644 --- a/crates/shirabe/tests/util/tls_helper_test.rs +++ b/crates/shirabe/tests/util/tls_helper_test.rs @@ -1,13 +1,13 @@ //! ref: composer/tests/Composer/Test/Util/TlsHelperTest.php #[test] -#[ignore = "Composer\\Util\\TlsHelper is not yet ported"] +#[ignore = "TlsHelper::check_certificate_host not implemented in crates/shirabe/src"] fn test_check_certificate_host() { todo!() } #[test] -#[ignore = "Composer\\Util\\TlsHelper is not yet ported"] +#[ignore = "TlsHelper::get_certificate_names not implemented in crates/shirabe/src"] fn test_get_certificate_names() { todo!() } |
