aboutsummaryrefslogtreecommitdiffhomepage
path: root/crates/shirabe/tests/util
diff options
context:
space:
mode:
authornsfisis <nsfisis@gmail.com>2026-06-25 16:44:29 +0900
committernsfisis <nsfisis@gmail.com>2026-06-26 00:20:05 +0900
commitf5f429dbae0a3e2d8224c0b1e4edcef54805d286 (patch)
tree9f837baeeae6efa0ed926b181b8c273128d86c49 /crates/shirabe/tests/util
parent3a0d9340810a8808d963135a884f50d08442ac67 (diff)
downloadphp-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')
-rw-r--r--crates/shirabe/tests/util/remote_filesystem_test.rs207
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!()
}