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/tests/util | |
| 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/tests/util')
| -rw-r--r-- | crates/shirabe/tests/util/auth_helper_test.rs | 40 | ||||
| -rw-r--r-- | crates/shirabe/tests/util/bitbucket_test.rs | 30 | ||||
| -rw-r--r-- | crates/shirabe/tests/util/error_handler_test.rs | 6 | ||||
| -rw-r--r-- | crates/shirabe/tests/util/filesystem_test.rs | 752 | ||||
| -rw-r--r-- | crates/shirabe/tests/util/forgejo_test.rs | 4 | ||||
| -rw-r--r-- | crates/shirabe/tests/util/git_test.rs | 14 | ||||
| -rw-r--r-- | crates/shirabe/tests/util/github_test.rs | 4 | ||||
| -rw-r--r-- | crates/shirabe/tests/util/gitlab_test.rs | 4 | ||||
| -rw-r--r-- | crates/shirabe/tests/util/http/proxy_manager_test.rs | 202 | ||||
| -rw-r--r-- | crates/shirabe/tests/util/http_downloader_test.rs | 100 | ||||
| -rw-r--r-- | crates/shirabe/tests/util/metadata_minifier_test.rs | 2 | ||||
| -rw-r--r-- | crates/shirabe/tests/util/perforce_test.rs | 211 | ||||
| -rw-r--r-- | crates/shirabe/tests/util/process_executor_test.rs | 174 | ||||
| -rw-r--r-- | crates/shirabe/tests/util/remote_filesystem_test.rs | 26 | ||||
| -rw-r--r-- | crates/shirabe/tests/util/stream_context_factory_test.rs | 66 | ||||
| -rw-r--r-- | crates/shirabe/tests/util/svn_test.rs | 12 | ||||
| -rw-r--r-- | crates/shirabe/tests/util/tls_helper_test.rs | 4 |
17 files changed, 1455 insertions, 196 deletions
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!() } |
