diff options
| author | nsfisis <nsfisis@gmail.com> | 2026-06-25 16:44:29 +0900 |
|---|---|---|
| committer | nsfisis <nsfisis@gmail.com> | 2026-06-26 00:20:05 +0900 |
| commit | f5f429dbae0a3e2d8224c0b1e4edcef54805d286 (patch) | |
| tree | 9f837baeeae6efa0ed926b181b8c273128d86c49 /crates/shirabe/tests/util/remote_filesystem_test.rs | |
| parent | 3a0d9340810a8808d963135a884f50d08442ac67 (diff) | |
| download | php-shirabe-f5f429dbae0a3e2d8224c0b1e4edcef54805d286.tar.gz php-shirabe-f5f429dbae0a3e2d8224c0b1e4edcef54805d286.tar.zst php-shirabe-f5f429dbae0a3e2d8224c0b1e4edcef54805d286.zip | |
feat(http): reimplement CurlDownloader on reqwest; port 15 more tests
Replace the libcurl-shim CurlDownloader with a reqwest+tokio implementation per
the .ken sketch, resolving the construction panic that blocked command tests
(mock path via __new_mock is untouched). Port remote_filesystem (7), hg/svn
driver (4), zip_archiver/git_exclude_filter (4) tests. Fix hg/svn/git_exclude
regex-delimiter and svn result-propagation porting bugs.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Diffstat (limited to 'crates/shirabe/tests/util/remote_filesystem_test.rs')
| -rw-r--r-- | crates/shirabe/tests/util/remote_filesystem_test.rs | 207 |
1 files changed, 184 insertions, 23 deletions
diff --git a/crates/shirabe/tests/util/remote_filesystem_test.rs b/crates/shirabe/tests/util/remote_filesystem_test.rs index b39a2dd..a5d9d66 100644 --- a/crates/shirabe/tests/util/remote_filesystem_test.rs +++ b/crates/shirabe/tests/util/remote_filesystem_test.rs @@ -1,82 +1,243 @@ //! ref: composer/tests/Composer/Test/Util/RemoteFilesystemTest.php -// These mock IO/Config/HttpDownloader and use reflection to drive RemoteFilesystem option -// building and downloads; mocking/reflection are not available and a real HttpDownloader -// reaches curl_multi_init (todo!()). +use std::cell::RefCell; +use std::rc::Rc; + +use indexmap::IndexMap; +use shirabe::io::IOInterface; +use shirabe::util::RemoteFilesystem; +use shirabe_php_shim::{PhpMixed, STREAM_NOTIFY_FILE_SIZE_IS, STREAM_NOTIFY_PROGRESS, strpos}; + +use crate::config_stub::ConfigStubBuilder; +use crate::io_stub::IOStub; + +// Mirrors RemoteFilesystemTest::getConfigMock: get('github-domains') and +// get('gitlab-domains') return [], everything else returns null. add_authentication_options +// reads gitlab-domains, so seed it as an empty list. +fn config_mock() -> Rc<RefCell<shirabe::config::Config>> { + ConfigStubBuilder::new() + .with("github-domains", PhpMixed::List(vec![])) + .with("gitlab-domains", PhpMixed::List(vec![])) + .build_shared() +} + +// Mirrors RemoteFilesystemTest::callGetOptionsForUrl: build a RemoteFilesystem, set the +// private file_url, then invoke the private get_options_for_url with the given args. +fn call_get_options_for_url( + io: Rc<RefCell<dyn IOInterface>>, + origin_url: &str, + additional_options: IndexMap<String, PhpMixed>, + options: IndexMap<String, PhpMixed>, + file_url: &str, +) -> IndexMap<String, PhpMixed> { + let mut fs = RemoteFilesystem::new(io, config_mock(), options, false, None); + fs.__set_file_url(file_url); + fs.__get_options_for_url(origin_url, additional_options) +} + +fn http_header_list(res: &IndexMap<String, PhpMixed>) -> Option<Vec<String>> { + res.get("http") + .and_then(|v| v.as_array()) + .and_then(|http| http.get("header")) + .and_then(|v| v.as_list()) + .map(|list| { + list.iter() + .map(|v| v.as_string().unwrap_or("").to_string()) + .collect() + }) +} + #[test] -#[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!() + let io: Rc<RefCell<dyn IOInterface>> = + Rc::new(RefCell::new(IOStub::new().with_has_authentication(false))); + + let res = call_get_options_for_url( + io, + "http://example.org", + IndexMap::new(), + IndexMap::new(), + "", + ); + + assert!( + http_header_list(&res).is_some(), + "getOptions must return an array with headers" + ); } #[test] -#[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!() + let mut auth: IndexMap<String, Option<String>> = IndexMap::new(); + auth.insert("username".to_string(), Some("login".to_string())); + auth.insert("password".to_string(), Some("password".to_string())); + let io: Rc<RefCell<dyn IOInterface>> = Rc::new(RefCell::new( + IOStub::new() + .with_has_authentication(true) + .with_get_authentication(auth), + )); + + let options = call_get_options_for_url( + io, + "http://example.org", + IndexMap::new(), + IndexMap::new(), + "", + ); + + let mut found = false; + for header in http_header_list(&options).unwrap_or_default() { + if strpos(&header, "Authorization: Basic") == Some(0) { + found = true; + } + } + assert!(found, "getOptions must have an Authorization header"); } #[test] -#[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!() + let mut auth: IndexMap<String, Option<String>> = IndexMap::new(); + auth.insert("username".to_string(), None); + auth.insert("password".to_string(), None); + let io: Rc<RefCell<dyn IOInterface>> = Rc::new(RefCell::new( + IOStub::new() + .with_has_authentication(true) + .with_get_authentication(auth), + )); + + let mut ssl: IndexMap<String, PhpMixed> = IndexMap::new(); + ssl.insert("allow_self_signed".to_string(), PhpMixed::Bool(true)); + let mut stream_options: IndexMap<String, PhpMixed> = IndexMap::new(); + stream_options.insert("ssl".to_string(), PhpMixed::Array(ssl)); + + let res = call_get_options_for_url( + io, + "https://example.org", + IndexMap::new(), + stream_options, + "", + ); + + let allow_self_signed = res + .get("ssl") + .and_then(|v| v.as_array()) + .and_then(|ssl| ssl.get("allow_self_signed")) + .and_then(|v| v.as_bool()); + assert_eq!( + allow_self_signed, + Some(true), + "getOptions must return an array with a allow_self_signed set to true" + ); } #[test] -#[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!() + let mut auth: IndexMap<String, Option<String>> = IndexMap::new(); + auth.insert("username".to_string(), None); + auth.insert("password".to_string(), None); + let io: Rc<RefCell<dyn IOInterface>> = Rc::new(RefCell::new( + IOStub::new() + .with_has_authentication(true) + .with_get_authentication(auth), + )); + + let mut http: IndexMap<String, PhpMixed> = IndexMap::new(); + http.insert( + "header".to_string(), + PhpMixed::String("Foo: bar".to_string()), + ); + let mut additional_options: IndexMap<String, PhpMixed> = IndexMap::new(); + additional_options.insert("http".to_string(), PhpMixed::Array(http)); + + let res = call_get_options_for_url( + io, + "https://example.org", + additional_options, + IndexMap::new(), + "", + ); + + let headers = http_header_list(&res); + assert!( + headers.is_some(), + "getOptions must return an array with a http.header key" + ); + let headers = headers.unwrap(); + + let found = headers.iter().any(|header| header == "Foo: bar"); + assert!(found, "getOptions must have a Foo: bar header"); + assert!(headers.len() > 1); } #[test] -#[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!() + let io: Rc<RefCell<dyn IOInterface>> = Rc::new(RefCell::new(IOStub::new())); + let mut fs = RemoteFilesystem::new(io, config_mock(), IndexMap::new(), false, None); + fs.__callback_get(STREAM_NOTIFY_FILE_SIZE_IS, 0, Some(String::new()), 0, 0, 20) + .unwrap(); + assert_eq!(20, fs.__bytes_max()); } #[test] -#[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!() + let io: Rc<RefCell<dyn IOInterface>> = Rc::new(RefCell::new(IOStub::new())); + let mut fs = RemoteFilesystem::new(io, config_mock(), IndexMap::new(), false, None); + fs.__set_bytes_max(20); + fs.__set_progress(true); + + fs.__callback_get(STREAM_NOTIFY_PROGRESS, 0, Some(String::new()), 0, 10, 20) + .unwrap(); + assert_eq!(Some(50), fs.__last_progress()); } #[test] -#[ignore = "requires a mocked IOInterface and ReflectionMethod to invoke private callback_get; no mocking/reflection infrastructure exists"] fn test_callback_get_passes_through404() { - todo!() + let io: Rc<RefCell<dyn IOInterface>> = Rc::new(RefCell::new(IOStub::new())); + let mut fs = RemoteFilesystem::new(io, config_mock(), IndexMap::new(), false, None); + + fs.__callback_get( + shirabe_php_shim::STREAM_NOTIFY_FAILURE, + 0, + Some("HTTP/1.1 404 Not Found".to_string()), + 404, + 0, + 0, + ) + .unwrap(); } #[test] -#[ignore = "requires a mocked IOInterface and a real get_contents which bottoms at curl_multi_init (todo!()); no mocking infrastructure exists"] +#[ignore = "real get_contents bottoms out at curl_multi_init (todo!()) in StreamContextFactory; no network/stream layer is modeled"] fn test_get_contents() { todo!() } #[test] -#[ignore = "requires a mocked IOInterface and a real copy which bottoms at curl_multi_init (todo!()); no mocking infrastructure exists"] +#[ignore = "real copy bottoms out at curl_multi_init (todo!()) in StreamContextFactory; no network/stream layer is modeled"] fn test_copy() { todo!() } #[test] -#[ignore = "requires a MockObject subclass of RemoteFilesystem overriding private get_remote_contents; no mocking infrastructure exists"] +#[ignore = "requires a MockObject subclass of RemoteFilesystem overriding private get_remote_contents; no subclass-mocking infrastructure exists"] fn test_copy_with_no_retry_on_failure() { todo!() } #[test] -#[ignore = "requires MockObject subclasses overriding RemoteFilesystem::get_remote_contents and AuthHelper::prompt_auth_if_needed; no mocking infrastructure exists"] +#[ignore = "requires MockObject subclasses overriding RemoteFilesystem::get_remote_contents and AuthHelper::prompt_auth_if_needed; no subclass-mocking infrastructure exists"] fn test_copy_with_success_on_retry() { todo!() } #[test] -#[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"] +#[ignore = "get_tls_defaults validates the (nonexistent) cafile and errors; constructor swallows it, so no ssl defaults are produced. Faithful porting needs CaBundle::validate_ca_file semantics for a missing file"] fn test_get_options_for_url_creates_secure_tls_defaults() { todo!() } #[test] -#[ignore = "requires a mocked ConsoleIO (getMockBuilder disableOriginalConstructor) and a real getContents network download reaching curl_multi_init (todo!()); no mocking infrastructure exists"] +#[ignore = "real getContents network download reaches curl_multi_init (todo!()); no network layer is modeled"] fn test_bit_bucket_public_download() { todo!() } |
