diff options
| author | nsfisis <nsfisis@gmail.com> | 2026-06-25 15:24:55 +0900 |
|---|---|---|
| committer | nsfisis <nsfisis@gmail.com> | 2026-06-26 00:20:05 +0900 |
| commit | d4cdccb8de8758bd46a12283f8df90e020327b99 (patch) | |
| tree | cd7c928cff2615a0bd599cf1f9e09c9852c399d7 /crates/shirabe/tests/downloader | |
| parent | 8117e5450693d19726da877bce8aacf5c7aa53af (diff) | |
| download | php-shirabe-d4cdccb8de8758bd46a12283f8df90e020327b99.tar.gz php-shirabe-d4cdccb8de8758bd46a12283f8df90e020327b99.tar.zst php-shirabe-d4cdccb8de8758bd46a12283f8df90e020327b99.zip | |
test: port 70 perforce/repository/downloader/installer/dispatcher tests
Port perforce (36), locker (10), composer_repository (7), installation_manager
(6), file_downloader (5), and event_dispatcher (6) tests via the mock infra.
Fix production porting bugs surfaced en route: BufferIO::get_output look-behind
regex, ComposerRepository list-form package iteration and initialize dispatch,
gethostname and spl_autoload_functions shims; add EventDispatcher get_listeners
test seam.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Diffstat (limited to 'crates/shirabe/tests/downloader')
| -rw-r--r-- | crates/shirabe/tests/downloader/file_downloader_test.rs | 237 |
1 files changed, 209 insertions, 28 deletions
diff --git a/crates/shirabe/tests/downloader/file_downloader_test.rs b/crates/shirabe/tests/downloader/file_downloader_test.rs index b99dd93..cb09c74 100644 --- a/crates/shirabe/tests/downloader/file_downloader_test.rs +++ b/crates/shirabe/tests/downloader/file_downloader_test.rs @@ -1,71 +1,252 @@ //! ref: composer/tests/Composer/Test/Downloader/FileDownloaderTest.php -fn set_up() { - // The HttpDownloader mock (disableOriginalConstructor) is not ported. - todo!() +use std::cell::RefCell; +use std::rc::Rc; + +use indexmap::IndexMap; +use serial_test::serial; +use shirabe::config::Config; +use shirabe::downloader::DownloaderInterface; +use shirabe::downloader::FileDownloader; +use shirabe::io::IOInterface; +use shirabe::io::null_io::NullIO; +use shirabe::package::handle::{CompletePackageHandle, PackageInterfaceHandle}; +use shirabe::util::HttpDownloader; +use shirabe::util::filesystem::Filesystem; +use shirabe::util::r#loop::Loop; +use shirabe_php_shim::{ + InvalidArgumentException, PhpMixed, RuntimeException, UnexpectedValueException, +}; +use shirabe_semver::VersionParser; +use tempfile::TempDir; + +use crate::http_downloader_mock::get_http_downloader_mock; +use shirabe::util::http_downloader::HttpDownloaderMockHandler; + +/// ref: TestCase::getPackage (default class CompletePackage) +fn get_package(name: &str, version: &str) -> PackageInterfaceHandle { + let norm_version = VersionParser.normalize(version, None).unwrap(); + CompletePackageHandle::new(name.to_string(), norm_version, version.to_string()).into() +} + +fn run<F: std::future::Future>(future: F) -> F::Output { + tokio::runtime::Builder::new_current_thread() + .build() + .unwrap() + .block_on(future) +} + +/// ref: TestCase::getConfig +fn get_config(config_options: IndexMap<String, PhpMixed>) -> Rc<RefCell<Config>> { + let mut config = Config::new(false, None); + if !config_options.is_empty() { + let mut merged: IndexMap<String, PhpMixed> = IndexMap::new(); + merged.insert("config".to_string(), PhpMixed::Array(config_options)); + config.merge(&merged, "test"); + } + Rc::new(RefCell::new(config)) +} + +/// The PHP `getDownloader` builds a HttpDownloader mock whose `addCopy` resolves to a 200 Response +/// with body `'file~'`. Here that is mirrored by a non-strict HttpDownloaderMock with a default +/// handler returning a 200/`file~` response for any URL. The mock never writes the destination file, +/// so the verification step throws "could not be saved to" exactly as in PHP. +fn get_downloader( + io: Option<Rc<RefCell<dyn IOInterface>>>, + config: Option<Rc<RefCell<Config>>>, +) -> ( + FileDownloader, + Rc<RefCell<HttpDownloader>>, + crate::http_downloader_mock::HttpDownloaderMockGuard, +) { + let io = io.unwrap_or_else(|| Rc::new(RefCell::new(NullIO::new()))); + let config = config.unwrap_or_else(|| Rc::new(RefCell::new(Config::new(false, None)))); + + let (http_downloader, guard) = get_http_downloader_mock( + Vec::new(), + false, + HttpDownloaderMockHandler { + status: 200, + body: "file~".to_string(), + headers: Vec::new(), + }, + ); + + let downloader = + FileDownloader::new(io, config, http_downloader.clone(), None, None, None, None); + + (downloader, http_downloader, guard) } -// These construct a FileDownloader with a mocked IO/HttpDownloader (curl_multi_init todo!()) -// and a mocked Cache/Package to drive download/checksum behaviour. #[test] -#[ignore = "requires PHPUnit mock of HttpDownloader::addCopy (getDownloader helper); no mocking framework and real HttpDownloader reaches curl_multi_init todo!()"] +#[serial] fn test_download_for_package_without_dist_reference() { - set_up(); - todo!() + let package = get_package("dummy/pkg", "1.0.0"); + + let (mut downloader, _http_downloader, _guard) = get_downloader(None, None); + let result = run(downloader.download(package, "/path", None, true)); + + let e = result.expect_err("expected InvalidArgumentException"); + assert!( + e.downcast_ref::<InvalidArgumentException>().is_some(), + "expected InvalidArgumentException, got: {e}" + ); } #[test] -#[ignore = "requires PHPUnit mock of HttpDownloader::addCopy (getDownloader helper); no mocking framework and real HttpDownloader reaches curl_multi_init todo!()"] +#[serial] fn test_download_to_existing_file() { - set_up(); - todo!() + let package = get_package("dummy/pkg", "1.0.0"); + package.set_dist_url(Some("url".to_string())); + + // createTempFile(getUniqueTmpDirectory()): a regular file used as the download target path. + let tmp_dir = TempDir::new().unwrap(); + let path = tmp_dir.path().join("composer_test_file"); + std::fs::write(&path, b"").unwrap(); + let path = path.to_string_lossy().into_owned(); + + let (mut downloader, _http_downloader, _guard) = get_downloader(None, None); + + let result = run(downloader.download(package, &path, None, true)); + + let e = result.expect_err("download to an existing file was expected to throw"); + assert!( + e.downcast_ref::<RuntimeException>().is_some(), + "expected RuntimeException, got: {e}" + ); + assert!( + e.to_string().contains("exists and is not a directory"), + "unexpected message: {e}" + ); } #[test] -#[ignore = "requires PHPUnit mock of HttpDownloader::addCopy (getDownloader helper) and ReflectionMethod on private getFileName; no mocking/reflection framework"] +#[serial] fn test_get_file_name() { - set_up(); - todo!() + let package = get_package("dummy/pkg", "1.0.0"); + package.set_dist_url(Some("http://example.com/script.js".to_string())); + + let mut config_options: IndexMap<String, PhpMixed> = IndexMap::new(); + config_options.insert( + "vendor-dir".to_string(), + PhpMixed::String("/vendor".to_string()), + ); + let config = get_config(config_options); + + let (downloader, _http_downloader, _guard) = get_downloader(None, Some(config)); + + let file_name = downloader.__get_file_name(package, "/path"); + let re = regex::Regex::new(r"/vendor/composer/tmp-[a-z0-9]+\.js").unwrap(); + assert!(re.is_match(&file_name), "unexpected file name: {file_name}"); } #[test] -#[ignore = "requires PHPUnit mock of HttpDownloader::addCopy and IOInterface::write callback; no mocking framework and real HttpDownloader reaches curl_multi_init todo!()"] +#[serial] fn test_download_but_file_is_unsaved() { - set_up(); - todo!() + let package = get_package("dummy/pkg", "1.0.0"); + package.set_dist_url(Some("http://example.com/script.js".to_string())); + + let tmp_dir = TempDir::new().unwrap(); + let path = tmp_dir.path().to_string_lossy().into_owned(); + + // The PHP IOMock write callback unlinks the half-written script.js; the Rust mock never writes + // the destination file in the first place, so a NullIO suffices to reproduce the unsaved-file path. + let mut config_options: IndexMap<String, PhpMixed> = IndexMap::new(); + config_options.insert( + "vendor-dir".to_string(), + PhpMixed::String(format!("{}/vendor", path)), + ); + let config = get_config(config_options); + + let (mut downloader, http_downloader, _guard) = get_downloader(None, Some(config)); + + let mut loop_ = Loop::new(http_downloader, None); + let promise = Box::pin(async { + downloader + .download(package.clone(), &path, None, true) + .await + .map(|_| ()) + }); + let result = run(loop_.wait(vec![promise], None)); + + let e = result.expect_err("download was expected to throw"); + assert!( + e.downcast_ref::<UnexpectedValueException>().is_some(), + "expected UnexpectedValueException, got: {e}" + ); + assert!( + e.to_string().contains("could not be saved to"), + "unexpected message: {e}" + ); } #[test] -#[ignore = "requires PHPUnit mocks of Cache::copyTo/copyFrom and HttpDownloader::addCopy with assertion callbacks; no mocking framework"] +#[ignore = "requires PHPUnit mocks of Cache::copyTo/copyFrom asserting on $cacheKey plus PreFileDownloadEvent::setProcessedUrl dispatch, which is TODO(plugin) in FileDownloader::download"] fn test_download_with_custom_processed_url() { - set_up(); todo!() } #[test] -#[ignore = "requires PHPUnit mocks of Cache::copyTo/copyFrom and HttpDownloader::addCopy with assertion callbacks; no mocking framework"] +#[ignore = "requires PHPUnit mocks of Cache::copyTo/copyFrom asserting on $cacheKey plus PreFileDownloadEvent::setCustomCacheKey dispatch, which is TODO(plugin) in FileDownloader::download"] fn test_download_with_custom_cache_key() { - set_up(); todo!() } #[test] -#[ignore = "requires PHPUnit mock of Cache::gcIsNecessary/gc with expectation tracking; no mocking framework"] +#[ignore = "requires a Cache mock with gcIsNecessary/gc expectation tracking; Cache is a concrete struct with no test hook for asserting gc() was called once"] fn test_cache_garbage_collection_is_called() { - set_up(); todo!() } #[test] -#[ignore = "requires PHPUnit mock of Filesystem and HttpDownloader::addCopy plus ReflectionMethod on private getFileName; no mocking/reflection framework"] +#[serial] fn test_download_file_with_invalid_checksum() { - set_up(); - todo!() + let package = get_package("dummy/pkg", "1.0.0"); + package.set_dist_url(Some("http://example.com/script.js".to_string())); + package.__set_dist_sha1_checksum(Some("invalid".to_string())); + + let tmp_dir = TempDir::new().unwrap(); + let path = tmp_dir.path().to_string_lossy().into_owned(); + let mut config_options: IndexMap<String, PhpMixed> = IndexMap::new(); + config_options.insert( + "vendor-dir".to_string(), + PhpMixed::String(format!("{}/vendor", path)), + ); + let config = get_config(config_options); + + // The PHP test injects a Filesystem mock so cleanup is a no-op; a real Filesystem behaves the + // same here since nothing under the temp dir needs preserving. + let (mut downloader, http_downloader, _guard) = get_downloader(None, Some(config)); + + // make sure the file expected to be downloaded is on disk already + let dl_file = downloader.__get_file_name(package.clone(), &path); + let dir = std::path::Path::new(&dl_file).parent().unwrap(); + std::fs::create_dir_all(dir).unwrap(); + std::fs::write(&dl_file, b"").unwrap(); + + let mut loop_ = Loop::new(http_downloader, None); + let promise = Box::pin(async { + downloader + .download(package.clone(), &path, None, true) + .await + .map(|_| ()) + }); + let result = run(loop_.wait(vec![promise], None)); + + let e = result.expect_err("download was expected to throw"); + assert!( + e.downcast_ref::<UnexpectedValueException>().is_some(), + "expected UnexpectedValueException, got: {e}" + ); + assert!( + e.to_string().contains("checksum verification"), + "unexpected message: {e}" + ); } #[test] -#[ignore = "requires PHPUnit mocks of Filesystem::removeDirectoryAsync/normalizePath, HttpDownloader::addCopy, getIOMock expectations and ReflectionMethod on private getFileName; no mocking/reflection framework"] +#[ignore = "requires a Filesystem mock of removeDirectoryAsync/normalizePath plus IOMock output expectations; Filesystem is a concrete struct with no async-removal test hook"] fn test_downgrade_shows_appropriate_message() { - set_up(); + let _ = Filesystem::new(None); todo!() } |
