aboutsummaryrefslogtreecommitdiffhomepage
path: root/crates/shirabe/tests
diff options
context:
space:
mode:
authornsfisis <nsfisis@gmail.com>2026-06-25 16:27:53 +0900
committernsfisis <nsfisis@gmail.com>2026-06-26 00:20:05 +0900
commit3a0d9340810a8808d963135a884f50d08442ac67 (patch)
tree9fbed3350a23c791f52e37209095e0db41962c87 /crates/shirabe/tests
parent46ac7242d526e13707dc140b7244e0fadf2219b9 (diff)
downloadphp-shirabe-3a0d9340810a8808d963135a884f50d08442ac67.tar.gz
php-shirabe-3a0d9340810a8808d963135a884f50d08442ac67.tar.zst
php-shirabe-3a0d9340810a8808d963135a884f50d08442ac67.zip
test: port 59 autoload/vcs/installer/util/command tests; fix output capture
Port autoload_generator (24), bitbucket (14), suggested_packages (11), git_driver (6), archive_manager (3), and a bump command test. Fix the ApplicationTester output-capture root cause (php://memory streams must be readable regardless of fopen mode). Implement posix_getuid/geteuid, the PCRE 'A' anchored modifier, php_strip_whitespace, stream_get_wrappers, is_callable scalars; fix preg_quote angle-bracket escaping and class-map parser regexes. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Diffstat (limited to 'crates/shirabe/tests')
-rw-r--r--crates/shirabe/tests/autoload/autoload_generator_test.rs1739
-rw-r--r--crates/shirabe/tests/autoload/main.rs3
-rw-r--r--crates/shirabe/tests/command/bump_command_test.rs70
-rw-r--r--crates/shirabe/tests/installer/main.rs2
-rw-r--r--crates/shirabe/tests/installer/suggested_packages_reporter_test.rs200
-rw-r--r--crates/shirabe/tests/package/archiver/archive_manager_test.rs134
-rw-r--r--crates/shirabe/tests/repository/vcs/git_driver_test.rs234
-rw-r--r--crates/shirabe/tests/util/bitbucket_test.rs640
8 files changed, 2805 insertions, 217 deletions
diff --git a/crates/shirabe/tests/autoload/autoload_generator_test.rs b/crates/shirabe/tests/autoload/autoload_generator_test.rs
index 72ac999..a3b833a 100644
--- a/crates/shirabe/tests/autoload/autoload_generator_test.rs
+++ b/crates/shirabe/tests/autoload/autoload_generator_test.rs
@@ -1,267 +1,1806 @@
//! ref: composer/tests/Composer/Test/Autoload/AutoloadGeneratorTest.php
-/// Creates the working/vendor temp directories, switches into the working dir, and
-/// builds the AutoloadGenerator from a mocked Config/InstallationManager/
-/// InstalledRepository/EventDispatcher and a BufferIO. The mocks and temp-dir
-/// helpers are not available here, so this remains a stub.
-fn set_up() {
- todo!()
+use std::cell::RefCell;
+use std::rc::Rc;
+
+use indexmap::IndexMap;
+use serial_test::serial;
+use tempfile::TempDir;
+
+use shirabe::autoload::AutoloadGenerator;
+use shirabe::composer::{ComposerHandle, PartialOrFullComposer};
+use shirabe::config::Config;
+use shirabe::event_dispatcher::EventDispatcher;
+use shirabe::installer::{InstallationManager, InstallerInterface};
+use shirabe::io::{BufferIO, IOInterface};
+use shirabe::package::handle::{AliasPackageHandle, PackageHandle, RootPackageHandle};
+use shirabe::package::{Link, PackageInterfaceHandle, RootPackageInterfaceHandle};
+use shirabe::repository::{
+ InstalledArrayRepository, InstalledRepositoryInterface, WritableRepositoryInterface,
+};
+use shirabe::util::http_downloader::HttpDownloader;
+use shirabe::util::r#loop::Loop;
+use shirabe_external_packages::symfony::console::output::output_interface;
+use shirabe_php_shim::PhpMixed;
+use shirabe_semver::constraint::{AnyConstraint, MatchAllConstraint, SimpleConstraint};
+
+use crate::config_stub::ConfigStubBuilder;
+
+/// The mock `InstallationManager::getInstallPath` used throughout the test: metapackages return
+/// null, every other package returns `vendorDir/<name>(/<targetDir>)`. Registered as an installer
+/// that supports every type so `InstallationManager::getInstallPath` routes to it.
+#[derive(Debug)]
+struct InstallPathStubInstaller {
+ vendor_dir: String,
}
-/// Restores the original working directory and removes the working/vendor
-/// directories created by `set_up`, which is itself a stub.
-fn tear_down() {
- todo!()
+#[async_trait::async_trait(?Send)]
+impl InstallerInterface for InstallPathStubInstaller {
+ fn supports(&self, _package_type: &str) -> bool {
+ true
+ }
+
+ fn is_installed(
+ &mut self,
+ _repo: &dyn InstalledRepositoryInterface,
+ _package: PackageInterfaceHandle,
+ ) -> bool {
+ true
+ }
+
+ async fn download(
+ &mut self,
+ _package: PackageInterfaceHandle,
+ _prev_package: Option<PackageInterfaceHandle>,
+ ) -> anyhow::Result<Option<PhpMixed>> {
+ Ok(None)
+ }
+
+ async fn prepare(
+ &mut self,
+ _type: &str,
+ _package: PackageInterfaceHandle,
+ _prev_package: Option<PackageInterfaceHandle>,
+ ) -> anyhow::Result<Option<PhpMixed>> {
+ Ok(None)
+ }
+
+ async fn install(
+ &mut self,
+ _repo: &mut dyn InstalledRepositoryInterface,
+ _package: PackageInterfaceHandle,
+ ) -> anyhow::Result<Option<PhpMixed>> {
+ Ok(None)
+ }
+
+ async fn update(
+ &mut self,
+ _repo: &mut dyn InstalledRepositoryInterface,
+ _initial: PackageInterfaceHandle,
+ _target: PackageInterfaceHandle,
+ ) -> anyhow::Result<Option<PhpMixed>> {
+ Ok(None)
+ }
+
+ async fn uninstall(
+ &mut self,
+ _repo: &mut dyn InstalledRepositoryInterface,
+ _package: PackageInterfaceHandle,
+ ) -> anyhow::Result<Option<PhpMixed>> {
+ Ok(None)
+ }
+
+ async fn cleanup(
+ &mut self,
+ _type: &str,
+ _package: PackageInterfaceHandle,
+ _prev_package: Option<PackageInterfaceHandle>,
+ ) -> anyhow::Result<Option<PhpMixed>> {
+ Ok(None)
+ }
+
+ fn get_install_path(&mut self, package: PackageInterfaceHandle) -> Option<String> {
+ if package.get_type() == "metapackage" {
+ return None;
+ }
+
+ let target_dir = package.get_target_dir();
+ let suffix = match target_dir {
+ Some(dir) if !dir.is_empty() => format!("/{}", dir),
+ _ => String::new(),
+ };
+
+ Some(format!(
+ "{}/{}{}",
+ self.vendor_dir,
+ package.get_name(),
+ suffix
+ ))
+ }
}
-struct TearDown;
+/// Mirrors the PHP `setUp`/`tearDown` lifecycle: a fresh temp working dir, a `composer-test-autoload`
+/// vendor dir inside it, `chdir`ed into the working dir, plus the mocked Config/InstallationManager/
+/// repository/EventDispatcher and BufferIO. The temp tree is removed and the cwd restored on drop.
+struct SetUp {
+ _temp_dir: TempDir,
+ prev_cwd: std::path::PathBuf,
+ working_dir: String,
+ vendor_dir: String,
+ repository: InstalledArrayRepository,
+ im: InstallationManager,
+ io: Rc<RefCell<BufferIO>>,
+ generator: AutoloadGenerator,
+ event_dispatcher: Rc<RefCell<EventDispatcher>>,
+}
-impl Drop for TearDown {
+impl Drop for SetUp {
fn drop(&mut self) {
- tear_down();
+ let _ = std::env::set_current_dir(&self.prev_cwd);
+ }
+}
+
+fn null_path(s: &str) -> String {
+ s.to_string()
+}
+
+fn set_up() -> SetUp {
+ let temp_dir = TempDir::new().unwrap();
+ let working_dir = temp_dir.path().to_str().unwrap().to_string();
+ let vendor_dir = format!("{}/composer-test-autoload", working_dir);
+ std::fs::create_dir_all(&vendor_dir).unwrap();
+
+ let prev_cwd = std::env::current_dir().unwrap();
+ std::env::set_current_dir(&working_dir).unwrap();
+
+ let io = Rc::new(RefCell::new(
+ BufferIO::new(String::new(), output_interface::VERBOSITY_NORMAL, None).unwrap(),
+ ));
+
+ // The PHP loop mock has its constructor disabled and is never exercised here, so a mock
+ // HttpDownloader (no real curl backend) stands in for the InstallationManager's loop.
+ let dispatcher_io: Rc<RefCell<dyn IOInterface>> = io.clone();
+ let config_for_downloader = Rc::new(RefCell::new(Config::new(false, None)));
+ let http_downloader = Rc::new(RefCell::new(HttpDownloader::__new_mock(
+ dispatcher_io.clone(),
+ config_for_downloader,
+ )));
+ let loop_ = Rc::new(RefCell::new(Loop::new(http_downloader, None)));
+
+ let mut im = InstallationManager::new(loop_, dispatcher_io.clone(), None);
+ im.add_installer(Box::new(InstallPathStubInstaller {
+ vendor_dir: vendor_dir.clone(),
+ }));
+
+ let repository = InstalledArrayRepository::new().unwrap();
+
+ // EventDispatcher constructor is disabled in PHP and dispatch is never called when run-scripts
+ // is off (the default), so a real dispatcher over an empty Composer is a faithful no-op stand-in.
+ let composer =
+ ComposerHandle::from_rc_unchecked(Rc::new(RefCell::new(PartialOrFullComposer::new_full())));
+ let event_dispatcher = Rc::new(RefCell::new(EventDispatcher::new(
+ composer.upcast().downgrade(),
+ dispatcher_io.clone(),
+ None,
+ )));
+
+ let generator = AutoloadGenerator::new(event_dispatcher.clone(), Some(dispatcher_io));
+
+ SetUp {
+ _temp_dir: temp_dir,
+ prev_cwd,
+ working_dir,
+ vendor_dir,
+ repository,
+ im,
+ io,
+ generator,
+ event_dispatcher,
+ }
+}
+
+impl SetUp {
+ /// Builds the mocked Config returning `vendor-dir`/`platform-check`/`use-include-path`, mirroring
+ /// the PHP `configValueMap`. Rebuilt per call because tests mutate `vendor_dir`.
+ fn config(&self) -> Config {
+ ConfigStubBuilder::new()
+ .with("vendor-dir", PhpMixed::String(self.vendor_dir.clone()))
+ .with("platform-check", PhpMixed::Bool(true))
+ .with("use-include-path", PhpMixed::Bool(false))
+ .build()
+ }
+
+ fn ensure_dir(&self, path: &str) {
+ std::fs::create_dir_all(path).unwrap();
+ }
+
+ fn put(&self, path: &str, contents: &str) {
+ let p = std::path::Path::new(path);
+ if let Some(parent) = p.parent() {
+ std::fs::create_dir_all(parent).unwrap();
+ }
+ std::fs::write(path, contents).unwrap();
+ }
+
+ fn set_canonical_packages(&mut self, packages: Vec<PackageInterfaceHandle>) {
+ for p in packages {
+ self.repository.add_package(p).unwrap();
+ }
}
}
-// These exercise AutoloadGenerator end-to-end: they build packages, write fixture files to
-// a temp dir, run dump() through a mocked InstalledRepository/EventDispatcher and compare
-// the generated autoload files. The integration setup (fixtures, mocks, filesystem) is not
-// ported yet.
+fn fixtures_dir() -> std::path::PathBuf {
+ std::path::Path::new(env!("CARGO_MANIFEST_DIR"))
+ .join("../../composer/tests/Composer/Test/Autoload/Fixtures")
+ .canonicalize()
+ .unwrap()
+}
+
+/// ref: AutoloadGeneratorTest::assertAutoloadFiles
+#[track_caller]
+fn assert_autoload_files(name: &str, dir: &str, r#type: &str) {
+ let a = fixtures_dir().join(format!("autoload_{}.php", name));
+ let b = format!("{}/autoload_{}.php", dir, r#type);
+ assert_file_content_equals(a.to_str().unwrap(), &b);
+}
+
+/// ref: AutoloadGeneratorTest::assertFileContentEquals
+#[track_caller]
+fn assert_file_content_equals(expected: &str, actual: &str) {
+ let exp = std::fs::read_to_string(expected)
+ .unwrap_or_else(|e| panic!("read {}: {}", expected, e))
+ .replace('\r', "");
+ let act = std::fs::read_to_string(actual)
+ .unwrap_or_else(|e| panic!("read {}: {}", actual, e))
+ .replace('\r', "");
+ assert_eq!(exp, act, "{} equals {}", expected, actual);
+}
+
+fn match_all() -> AnyConstraint {
+ AnyConstraint::MatchAll(MatchAllConstraint::new(None))
+}
+
+fn constraint(operator: &str, version: &str) -> AnyConstraint {
+ AnyConstraint::Simple(SimpleConstraint::new(
+ operator.to_string(),
+ version.to_string(),
+ None,
+ ))
+}
+
+fn link(source: &str, target: &str, constraint: AnyConstraint, description: Option<&str>) -> Link {
+ Link::new(
+ source.to_string(),
+ target.to_string(),
+ constraint,
+ description.map(|d| d.to_string()),
+ String::new(),
+ )
+}
+
+fn requires(links: Vec<(&str, Link)>) -> IndexMap<String, Link> {
+ links.into_iter().map(|(k, v)| (k.to_string(), v)).collect()
+}
+
+fn autoload(entries: Vec<(&str, PhpMixed)>) -> IndexMap<String, PhpMixed> {
+ entries
+ .into_iter()
+ .map(|(k, v)| (k.to_string(), v))
+ .collect()
+}
+
+fn str_list(items: &[&str]) -> PhpMixed {
+ PhpMixed::List(
+ items
+ .iter()
+ .map(|s| PhpMixed::String(s.to_string()))
+ .collect(),
+ )
+}
+
+fn str_map(entries: &[(&str, PhpMixed)]) -> PhpMixed {
+ PhpMixed::Array(
+ entries
+ .iter()
+ .map(|(k, v)| (k.to_string(), v.clone()))
+ .collect(),
+ )
+}
+
+fn pstr(v: &str) -> PhpMixed {
+ PhpMixed::String(v.to_string())
+}
+
+fn new_root_pkg(name: &str) -> RootPackageHandle {
+ RootPackageHandle::new(name.to_string(), null_path("1.0"), "1.0".to_string())
+}
+
+fn new_pkg(name: &str) -> PackageHandle {
+ PackageHandle::new(name.to_string(), "1.0".to_string(), "1.0".to_string())
+}
+
+fn dump(
+ s: &mut SetUp,
+ package: RootPackageInterfaceHandle,
+ scan_psr_packages: bool,
+ suffix: &str,
+) -> anyhow::Result<shirabe_class_map_generator::class_map::ClassMap> {
+ let config = s.config();
+ // Borrow splitting: take fields out so dump can hold &mut to several at once.
+ let SetUp {
+ repository,
+ im,
+ generator,
+ ..
+ } = s;
+ generator.dump(
+ &config,
+ repository,
+ package,
+ im,
+ "composer",
+ scan_psr_packages,
+ Some(suffix.to_string()),
+ None,
+ false,
+ )
+}
+
#[test]
-#[ignore = "setUp needs mocked Config::get, InstallationManager::getInstallPath, InstalledRepositoryInterface::getCanonicalPackages and TestCase::getUniqueTmpDirectory/ensureDirectoryExistsAndClear; no mock infra exists"]
+#[serial]
fn test_root_package_autoloading() {
- todo!()
+ let mut s = set_up();
+ let package = new_root_pkg("root/a");
+ package.set_autoload(autoload(vec![
+ (
+ "psr-0",
+ str_map(&[
+ ("Main", pstr("src/")),
+ ("Lala", str_list(&["src/", "lib/"])),
+ ]),
+ ),
+ (
+ "psr-4",
+ str_map(&[
+ ("Acme\\Fruit\\", pstr("src-fruit/")),
+ ("Acme\\Cake\\", str_list(&["src-cake/", "lib-cake/"])),
+ ]),
+ ),
+ ("classmap", str_list(&["composersrc/"])),
+ ]));
+
+ s.ensure_dir(&format!("{}/composer", s.working_dir));
+ s.ensure_dir(&format!("{}/src/Lala/Test", s.working_dir));
+ s.ensure_dir(&format!("{}/lib", s.working_dir));
+ s.put(
+ &format!("{}/src/Lala/ClassMapMain.php", s.working_dir),
+ "<?php namespace Lala; class ClassMapMain {}",
+ );
+ s.put(
+ &format!("{}/src/Lala/Test/ClassMapMainTest.php", s.working_dir),
+ "<?php namespace Lala\\Test; class ClassMapMainTest {}",
+ );
+
+ s.ensure_dir(&format!("{}/src-fruit", s.working_dir));
+ s.ensure_dir(&format!("{}/src-cake", s.working_dir));
+ s.ensure_dir(&format!("{}/lib-cake", s.working_dir));
+ s.put(
+ &format!("{}/src-cake/ClassMapBar.php", s.working_dir),
+ "<?php namespace Acme\\Cake; class ClassMapBar {}",
+ );
+
+ s.ensure_dir(&format!("{}/composersrc", s.working_dir));
+ s.put(
+ &format!("{}/composersrc/foo.php", s.working_dir),
+ "<?php class ClassMapFoo {}",
+ );
+
+ let composer_out = format!("{}/composer", s.vendor_dir);
+ dump(&mut s, package.into(), true, "_1").unwrap();
+
+ assert_autoload_files("main", &composer_out, "namespaces");
+ assert_autoload_files("psr4", &composer_out, "psr4");
+ assert_autoload_files("classmap", &composer_out, "classmap");
}
#[test]
-#[ignore = "setUp needs mocked Config/InstallationManager/InstalledRepositoryInterface and TestCase tmp-dir helpers; no mock infra exists"]
+#[serial]
fn test_root_package_dev_autoloading() {
- todo!()
+ let mut s = set_up();
+ let package = new_root_pkg("root/a");
+ package.set_autoload(autoload(vec![(
+ "psr-0",
+ str_map(&[("Main", pstr("src/"))]),
+ )]));
+ package.set_dev_autoload(autoload(vec![
+ ("files", str_list(&["devfiles/foo.php"])),
+ ("psr-0", str_map(&[("Main", pstr("tests/"))])),
+ ]));
+
+ s.ensure_dir(&format!("{}/composer", s.working_dir));
+ s.ensure_dir(&format!("{}/src/Main", s.working_dir));
+ s.put(
+ &format!("{}/src/Main/ClassMain.php", s.working_dir),
+ "<?php namespace Main; class ClassMain {}",
+ );
+ s.ensure_dir(&format!("{}/devfiles", s.working_dir));
+ s.put(
+ &format!("{}/devfiles/foo.php", s.working_dir),
+ "<?php function foo() { echo \"foo\"; }",
+ );
+
+ let composer_out = format!("{}/composer", s.vendor_dir);
+ s.generator.set_dev_mode(true);
+ dump(&mut s, package.into(), true, "_1").unwrap();
+
+ assert_autoload_files("main5", &composer_out, "namespaces");
+ assert_autoload_files("classmap7", &composer_out, "classmap");
+ assert_autoload_files("files2", &composer_out, "files");
}
#[test]
-#[ignore = "setUp needs mocked Config/InstallationManager/InstalledRepositoryInterface and TestCase tmp-dir helpers; no mock infra exists"]
+#[serial]
fn test_root_package_dev_autoloading_disabled_by_default() {
- todo!()
+ let mut s = set_up();
+ let package = new_root_pkg("root/a");
+ package.set_autoload(autoload(vec![(
+ "psr-0",
+ str_map(&[("Main", pstr("src/"))]),
+ )]));
+ package.set_dev_autoload(autoload(vec![("files", str_list(&["devfiles/foo.php"]))]));
+
+ s.ensure_dir(&format!("{}/composer", s.working_dir));
+ s.ensure_dir(&format!("{}/src/Main", s.working_dir));
+ s.put(
+ &format!("{}/src/Main/ClassMain.php", s.working_dir),
+ "<?php namespace Main; class ClassMain {}",
+ );
+ s.ensure_dir(&format!("{}/devfiles", s.working_dir));
+ s.put(
+ &format!("{}/devfiles/foo.php", s.working_dir),
+ "<?php function foo() { echo \"foo\"; }",
+ );
+
+ let composer_out = format!("{}/composer", s.vendor_dir);
+ dump(&mut s, package.into(), true, "_1").unwrap();
+
+ assert_autoload_files("main4", &composer_out, "namespaces");
+ assert_autoload_files("classmap7", &composer_out, "classmap");
+ assert!(!std::path::Path::new(&format!("{}/autoload_files.php", composer_out)).is_file());
}
#[test]
-#[ignore = "setUp needs mocked Config/InstallationManager/InstalledRepositoryInterface and TestCase tmp-dir helpers; no mock infra exists"]
+#[serial]
fn test_vendor_dir_same_as_working_dir() {
- todo!()
+ let mut s = set_up();
+ s.vendor_dir = s.working_dir.clone();
+ // Re-register the install-path stub so getInstallPath uses the new vendor dir.
+ s.im.add_installer(Box::new(InstallPathStubInstaller {
+ vendor_dir: s.vendor_dir.clone(),
+ }));
+
+ let package = new_root_pkg("root/a");
+ package.set_autoload(autoload(vec![
+ (
+ "psr-0",
+ str_map(&[("Main", pstr("src/")), ("Lala", pstr("src/"))]),
+ ),
+ (
+ "psr-4",
+ str_map(&[
+ ("Acme\\Fruit\\", pstr("src-fruit/")),
+ ("Acme\\Cake\\", str_list(&["src-cake/", "lib-cake/"])),
+ ]),
+ ),
+ ("classmap", str_list(&["composersrc/"])),
+ ]));
+
+ s.ensure_dir(&format!("{}/composer", s.vendor_dir));
+ s.ensure_dir(&format!("{}/src/Main", s.vendor_dir));
+ s.put(
+ &format!("{}/src/Main/Foo.php", s.vendor_dir),
+ "<?php namespace Main; class Foo {}",
+ );
+ s.ensure_dir(&format!("{}/composersrc", s.vendor_dir));
+ s.put(
+ &format!("{}/composersrc/foo.php", s.vendor_dir),
+ "<?php class ClassMapFoo {}",
+ );
+
+ let composer_out = format!("{}/composer", s.vendor_dir);
+ dump(&mut s, package.into(), true, "_2").unwrap();
+ assert_autoload_files("main3", &composer_out, "namespaces");
+ assert_autoload_files("psr4_3", &composer_out, "psr4");
+ assert_autoload_files("classmap3", &composer_out, "classmap");
}
#[test]
-#[ignore = "setUp needs mocked Config/InstallationManager/InstalledRepositoryInterface and TestCase tmp-dir helpers; no mock infra exists"]
+#[serial]
fn test_root_package_autoloading_alternative_vendor_dir() {
- todo!()
+ let mut s = set_up();
+ let package = new_root_pkg("root/a");
+ package.set_autoload(autoload(vec![
+ (
+ "psr-0",
+ str_map(&[("Main", pstr("src/")), ("Lala", pstr("src/"))]),
+ ),
+ (
+ "psr-4",
+ str_map(&[
+ ("Acme\\Fruit\\", pstr("src-fruit/")),
+ ("Acme\\Cake\\", str_list(&["src-cake/", "lib-cake/"])),
+ ]),
+ ),
+ ("classmap", str_list(&["composersrc/"])),
+ ]));
+
+ s.vendor_dir = format!("{}/subdir", s.vendor_dir);
+ s.im.add_installer(Box::new(InstallPathStubInstaller {
+ vendor_dir: s.vendor_dir.clone(),
+ }));
+
+ s.ensure_dir(&format!("{}/composer", s.vendor_dir));
+ s.ensure_dir(&format!("{}/src", s.working_dir));
+ s.ensure_dir(&format!("{}/composersrc", s.working_dir));
+ s.put(
+ &format!("{}/composersrc/foo.php", s.working_dir),
+ "<?php class ClassMapFoo {}",
+ );
+
+ let composer_out = format!("{}/composer", s.vendor_dir);
+ dump(&mut s, package.into(), false, "_3").unwrap();
+ assert_autoload_files("main2", &composer_out, "namespaces");
+ assert_autoload_files("psr4_2", &composer_out, "psr4");
+ assert_autoload_files("classmap2", &composer_out, "classmap");
}
#[test]
-#[ignore = "setUp needs mocked Config/InstallationManager/InstalledRepositoryInterface and TestCase tmp-dir helpers; no mock infra exists"]
+#[serial]
+#[ignore = "autoload_real.php/autoload_static.php fixtures track a newer Composer template (single blank lines + $filesToLoad/$requireFile block) than the current AutoloadGenerator port emits; needs production template alignment"]
fn test_root_package_autoloading_with_target_dir() {
- todo!()
+ let mut s = set_up();
+ let package = new_root_pkg("root/a");
+ package.set_autoload(autoload(vec![
+ (
+ "psr-0",
+ str_map(&[("Main\\Foo", pstr("")), ("Main\\Bar", pstr(""))]),
+ ),
+ ("classmap", str_list(&["Main/Foo/src", "lib"])),
+ ("files", str_list(&["foo.php", "Main/Foo/bar.php"])),
+ ]));
+ package.__set_target_dir(Some("Main/Foo/".to_string()));
+
+ s.ensure_dir(&format!("{}/a", s.vendor_dir));
+ s.ensure_dir(&format!("{}/src", s.working_dir));
+ s.ensure_dir(&format!("{}/lib", s.working_dir));
+ s.put(
+ &format!("{}/src/rootfoo.php", s.working_dir),
+ "<?php class ClassMapFoo {}",
+ );
+ s.put(
+ &format!("{}/lib/rootbar.php", s.working_dir),
+ "<?php class ClassMapBar {}",
+ );
+ s.put(
+ &format!("{}/foo.php", s.working_dir),
+ "<?php class FilesFoo {}",
+ );
+ s.put(
+ &format!("{}/bar.php", s.working_dir),
+ "<?php class FilesBar {}",
+ );
+
+ let vendor = s.vendor_dir.clone();
+ let composer_out = format!("{}/composer", vendor);
+ dump(&mut s, package.into(), false, "TargetDir").unwrap();
+
+ let fx = fixtures_dir();
+ assert_file_content_equals(
+ fx.join("autoload_target_dir.php").to_str().unwrap(),
+ &format!("{}/autoload.php", vendor),
+ );
+ assert_file_content_equals(
+ fx.join("autoload_real_target_dir.php").to_str().unwrap(),
+ &format!("{}/autoload_real.php", composer_out),
+ );
+ assert_file_content_equals(
+ fx.join("autoload_static_target_dir.php").to_str().unwrap(),
+ &format!("{}/autoload_static.php", composer_out),
+ );
+ assert_file_content_equals(
+ fx.join("autoload_files_target_dir.php").to_str().unwrap(),
+ &format!("{}/autoload_files.php", composer_out),
+ );
+ assert_autoload_files("classmap6", &composer_out, "classmap");
}
#[test]
-#[ignore = "setUp needs mocked Config/InstallationManager/InstalledRepositoryInterface and TestCase tmp-dir helpers; no mock infra exists"]
+#[serial]
fn test_duplicate_files_warning() {
- todo!()
+ let mut s = set_up();
+ let package = new_root_pkg("root/a");
+ package.set_autoload(autoload(vec![(
+ "files",
+ str_list(&["foo.php", "bar.php", "./foo.php", "././foo.php"]),
+ )]));
+
+ s.ensure_dir(&format!("{}/a", s.vendor_dir));
+ s.ensure_dir(&format!("{}/src", s.working_dir));
+ s.ensure_dir(&format!("{}/lib", s.working_dir));
+ s.put(
+ &format!("{}/foo.php", s.working_dir),
+ "<?php class FilesFoo {}",
+ );
+ s.put(
+ &format!("{}/bar.php", s.working_dir),
+ "<?php class FilesBar {}",
+ );
+
+ let vendor = s.vendor_dir.clone();
+ let composer_out = format!("{}/composer", vendor);
+ dump(&mut s, package.into(), false, "FilesWarning").unwrap();
+
+ assert_file_content_equals(
+ fixtures_dir()
+ .join("autoload_files_duplicates.php")
+ .to_str()
+ .unwrap(),
+ &format!("{}/autoload_files.php", composer_out),
+ );
+ let expected = "<warning>The following \"files\" autoload rules are included multiple times, this may cause issues and should be resolved:</warning>\n<warning> - $baseDir . '/foo.php'</warning>\n";
+ assert_eq!(expected, s.io.borrow().get_output());
}
#[test]
-#[ignore = "setUp needs mocked Config/InstallationManager/InstalledRepositoryInterface and TestCase tmp-dir helpers; no mock infra exists"]
+#[serial]
fn test_vendors_autoloading() {
- todo!()
+ let mut s = set_up();
+ let package = new_root_pkg("root/a");
+ package.set_requires(requires(vec![
+ ("a/a", link("a", "a/a", match_all(), None)),
+ ("b/b", link("a", "b/b", match_all(), None)),
+ ]));
+
+ let a = new_pkg("a/a");
+ let b = new_pkg("b/b");
+ let c = AliasPackageHandle::new(b.clone(), "1.2".to_string(), "1.2".to_string());
+ a.__set_autoload(autoload(vec![(
+ "psr-0",
+ str_map(&[("A", pstr("src/")), ("A\\B", pstr("lib/"))]),
+ )]));
+ b.__set_autoload(autoload(vec![(
+ "psr-0",
+ str_map(&[("B\\Sub\\Name", pstr("src/"))]),
+ )]));
+
+ s.set_canonical_packages(vec![a.into(), b.into(), c.into()]);
+
+ s.ensure_dir(&format!("{}/composer", s.vendor_dir));
+ s.ensure_dir(&format!("{}/a/a/src", s.vendor_dir));
+ s.ensure_dir(&format!("{}/a/a/lib", s.vendor_dir));
+ s.ensure_dir(&format!("{}/b/b/src", s.vendor_dir));
+
+ let composer_out = format!("{}/composer", s.vendor_dir);
+ dump(&mut s, package.into(), false, "_5").unwrap();
+ assert_autoload_files("vendors", &composer_out, "namespaces");
+ assert!(std::path::Path::new(&format!("{}/autoload_classmap.php", composer_out)).exists());
}
#[test]
-#[ignore = "setUp needs mocked Config/InstallationManager/InstalledRepositoryInterface and TestCase tmp-dir helpers; no mock infra exists"]
+#[serial]
fn test_vendors_autoloading_with_metapackages() {
- todo!()
+ let mut s = set_up();
+ let package = new_root_pkg("root/a");
+ package.set_requires(requires(vec![("a/a", link("a", "a/a", match_all(), None))]));
+
+ let a = new_pkg("a/a");
+ let b = new_pkg("b/b");
+ let c = AliasPackageHandle::new(b.clone(), "1.2".to_string(), "1.2".to_string());
+ a.__set_autoload(autoload(vec![(
+ "psr-0",
+ str_map(&[("A", pstr("src/")), ("A\\B", pstr("lib/"))]),
+ )]));
+ b.__set_autoload(autoload(vec![(
+ "psr-0",
+ str_map(&[("B\\Sub\\Name", pstr("src/"))]),
+ )]));
+ a.__set_type("metapackage".to_string());
+ a.__set_requires(requires(vec![(
+ "b/b",
+ link("a/a", "b/b", match_all(), None),
+ )]));
+
+ s.set_canonical_packages(vec![a.into(), b.into(), c.into()]);
+
+ s.ensure_dir(&format!("{}/composer", s.vendor_dir));
+ s.ensure_dir(&format!("{}/b/b/src", s.vendor_dir));
+ s.ensure_dir(&format!("{}/a/a/src", s.vendor_dir));
+ s.ensure_dir(&format!("{}/a/a/lib", s.vendor_dir));
+
+ let composer_out = format!("{}/composer", s.vendor_dir);
+ dump(&mut s, package.into(), false, "_5").unwrap();
+ assert_autoload_files("vendors_meta", &composer_out, "namespaces");
+ assert!(std::path::Path::new(&format!("{}/autoload_classmap.php", composer_out)).exists());
}
#[test]
-#[ignore = "setUp needs mocked Config/InstallationManager/InstalledRepositoryInterface and TestCase tmp-dir helpers; no mock infra exists"]
+#[serial]
fn test_non_dev_autoload_exclusion_with_recursion() {
- todo!()
+ let mut s = set_up();
+ let package = new_root_pkg("root/a");
+ package.set_requires(requires(vec![("a/a", link("a", "a/a", match_all(), None))]));
+
+ let a = new_pkg("a/a");
+ let b = new_pkg("b/b");
+ a.__set_autoload(autoload(vec![(
+ "psr-0",
+ str_map(&[("A", pstr("src/")), ("A\\B", pstr("lib/"))]),
+ )]));
+ a.__set_requires(requires(vec![(
+ "b/b",
+ link("a/a", "b/b", match_all(), None),
+ )]));
+ b.__set_autoload(autoload(vec![(
+ "psr-0",
+ str_map(&[("B\\Sub\\Name", pstr("src/"))]),
+ )]));
+ b.__set_requires(requires(vec![(
+ "a/a",
+ link("b/b", "a/a", match_all(), None),
+ )]));
+
+ s.set_canonical_packages(vec![a.into(), b.into()]);
+
+ s.ensure_dir(&format!("{}/composer", s.vendor_dir));
+ s.ensure_dir(&format!("{}/a/a/src", s.vendor_dir));
+ s.ensure_dir(&format!("{}/a/a/lib", s.vendor_dir));
+ s.ensure_dir(&format!("{}/b/b/src", s.vendor_dir));
+
+ let composer_out = format!("{}/composer", s.vendor_dir);
+ dump(&mut s, package.into(), false, "_5").unwrap();
+ assert_autoload_files("vendors", &composer_out, "namespaces");
+ assert!(std::path::Path::new(&format!("{}/autoload_classmap.php", composer_out)).exists());
}
#[test]
-#[ignore = "setUp needs mocked Config/InstallationManager/InstalledRepositoryInterface and TestCase tmp-dir helpers; no mock infra exists"]
+#[serial]
fn test_non_dev_autoload_should_include_replaced_packages() {
- todo!()
+ let mut s = set_up();
+ let package = new_root_pkg("root/a");
+ package.set_requires(requires(vec![("a/a", link("a", "a/a", match_all(), None))]));
+
+ let a = new_pkg("a/a");
+ let b = new_pkg("b/b");
+ a.__set_requires(requires(vec![(
+ "b/c",
+ link("a/a", "b/c", match_all(), None),
+ )]));
+ b.__set_autoload(autoload(vec![("psr-4", str_map(&[("B\\", pstr("src/"))]))]));
+ b.__set_replaces(requires(vec![(
+ "b/c",
+ link(
+ "b/b",
+ "b/c",
+ constraint("==", "1.0"),
+ Some(Link::TYPE_REPLACE),
+ ),
+ )]));
+
+ s.set_canonical_packages(vec![a.into(), b.into()]);
+
+ s.ensure_dir(&format!("{}/b/b/src/C", s.vendor_dir));
+ s.put(
+ &format!("{}/b/b/src/C/C.php", s.vendor_dir),
+ "<?php namespace B\\C; class C {}",
+ );
+
+ let vendor = s.vendor_dir.clone();
+ let class_map = dump(&mut s, package.into(), true, "_5").unwrap();
+
+ let mut expected: IndexMap<String, String> = IndexMap::new();
+ expected.insert("B\\C\\C".to_string(), format!("{}/b/b/src/C/C.php", vendor));
+ expected.insert(
+ "Composer\\InstalledVersions".to_string(),
+ format!("{}/composer/InstalledVersions.php", vendor),
+ );
+ assert_eq!(&expected, class_map.get_map());
}
#[test]
-#[ignore = "setUp needs mocked Config/InstallationManager/InstalledRepositoryInterface and TestCase tmp-dir helpers; no mock infra exists"]
+#[serial]
fn test_non_dev_autoload_exclusion_with_recursion_replace() {
- todo!()
+ let mut s = set_up();
+ let package = new_root_pkg("root/a");
+ package.set_requires(requires(vec![("a/a", link("a", "a/a", match_all(), None))]));
+
+ let a = new_pkg("a/a");
+ let b = new_pkg("b/b");
+ a.__set_autoload(autoload(vec![(
+ "psr-0",
+ str_map(&[("A", pstr("src/")), ("A\\B", pstr("lib/"))]),
+ )]));
+ a.__set_requires(requires(vec![(
+ "c/c",
+ link("a/a", "c/c", match_all(), None),
+ )]));
+ b.__set_autoload(autoload(vec![(
+ "psr-0",
+ str_map(&[("B\\Sub\\Name", pstr("src/"))]),
+ )]));
+ b.__set_replaces(requires(vec![(
+ "c/c",
+ link("b/b", "c/c", match_all(), None),
+ )]));
+
+ s.set_canonical_packages(vec![a.into(), b.into()]);
+
+ s.ensure_dir(&format!("{}/composer", s.vendor_dir));
+ s.ensure_dir(&format!("{}/a/a/src", s.vendor_dir));
+ s.ensure_dir(&format!("{}/a/a/lib", s.vendor_dir));
+ s.ensure_dir(&format!("{}/b/b/src", s.vendor_dir));
+
+ let composer_out = format!("{}/composer", s.vendor_dir);
+ dump(&mut s, package.into(), false, "_5").unwrap();
+ assert_autoload_files("vendors", &composer_out, "namespaces");
+ assert!(std::path::Path::new(&format!("{}/autoload_classmap.php", composer_out)).exists());
}
#[test]
-#[ignore = "setUp needs mocked Config/InstallationManager/InstalledRepositoryInterface and TestCase tmp-dir helpers; no mock infra exists"]
+#[serial]
fn test_non_dev_autoload_replaces_nested_requirements() {
- todo!()
+ let mut s = set_up();
+ let package = new_root_pkg("root/a");
+ package.set_requires(requires(vec![("a/a", link("a", "a/a", match_all(), None))]));
+
+ let a = new_pkg("a/a");
+ let b = new_pkg("b/b");
+ let c = new_pkg("c/c");
+ let d = new_pkg("d/d");
+ let e = new_pkg("e/e");
+ a.__set_autoload(autoload(vec![("classmap", str_list(&["src/A.php"]))]));
+ a.__set_requires(requires(vec![(
+ "b/b",
+ link("a/a", "b/b", match_all(), None),
+ )]));
+ b.__set_autoload(autoload(vec![("classmap", str_list(&["src/B.php"]))]));
+ b.__set_requires(requires(vec![(
+ "e/e",
+ link("b/b", "e/e", match_all(), None),
+ )]));
+ c.__set_autoload(autoload(vec![("classmap", str_list(&["src/C.php"]))]));
+ c.__set_replaces(requires(vec![(
+ "b/b",
+ link("c/c", "b/b", match_all(), None),
+ )]));
+ c.__set_requires(requires(vec![(
+ "d/d",
+ link("c/c", "d/d", match_all(), None),
+ )]));
+ d.__set_autoload(autoload(vec![("classmap", str_list(&["src/D.php"]))]));
+ e.__set_autoload(autoload(vec![("classmap", str_list(&["src/E.php"]))]));
+
+ s.set_canonical_packages(vec![a.into(), b.into(), c.into(), d.into(), e.into()]);
+
+ for (name, file, class) in [
+ ("a/a", "A", "A"),
+ ("b/b", "B", "B"),
+ ("c/c", "C", "C"),
+ ("d/d", "D", "D"),
+ ("e/e", "E", "E"),
+ ] {
+ s.ensure_dir(&format!("{}/{}/src", s.vendor_dir, name));
+ s.put(
+ &format!("{}/{}/src/{}.php", s.vendor_dir, name, file),
+ &format!("<?php class {} {{}}", class),
+ );
+ }
+
+ let composer_out = format!("{}/composer", s.vendor_dir);
+ dump(&mut s, package.into(), false, "_5").unwrap();
+ assert_autoload_files("classmap9", &composer_out, "classmap");
}
#[test]
-#[ignore = "setUp needs mocked Config/InstallationManager/InstalledRepositoryInterface and TestCase tmp-dir helpers; no mock infra exists"]
+#[serial]
+#[ignore = "autoload_static.php getInitializer() fixture has a trailing blank line the current AutoloadGenerator template omits; needs production template alignment"]
fn test_phar_autoload() {
- todo!()
+ let mut s = set_up();
+ let package = new_root_pkg("root/a");
+ package.set_requires(requires(vec![("a/a", link("a", "a/a", match_all(), None))]));
+ package.set_autoload(autoload(vec![
+ (
+ "psr-0",
+ str_map(&[("Foo", pstr("foo.phar")), ("Bar", pstr("dir/bar.phar/src"))]),
+ ),
+ (
+ "psr-4",
+ str_map(&[
+ ("Baz\\", pstr("baz.phar")),
+ ("Qux\\", pstr("dir/qux.phar/src")),
+ ]),
+ ),
+ ]));
+
+ let vendor_package = new_pkg("a/a");
+ vendor_package.__set_autoload(autoload(vec![
+ (
+ "psr-0",
+ str_map(&[
+ ("Lorem", pstr("lorem.phar")),
+ ("Ipsum", pstr("dir/ipsum.phar/src")),
+ ]),
+ ),
+ (
+ "psr-4",
+ str_map(&[
+ ("Dolor\\", pstr("dolor.phar")),
+ ("Sit\\", pstr("dir/sit.phar/src")),
+ ]),
+ ),
+ ]));
+
+ s.set_canonical_packages(vec![vendor_package.into()]);
+
+ let composer_out = format!("{}/composer", s.vendor_dir);
+ dump(&mut s, package.into(), true, "Phar").unwrap();
+ assert_autoload_files("phar", &composer_out, "namespaces");
+ assert_autoload_files("phar_psr4", &composer_out, "psr4");
+ assert_autoload_files("phar_static", &composer_out, "static");
}
#[test]
-#[ignore = "setUp needs mocked Config/InstallationManager/InstalledRepositoryInterface and TestCase tmp-dir helpers; no mock infra exists"]
+#[serial]
fn test_psr_to_class_map_ignores_non_existing_dir() {
- todo!()
+ let mut s = set_up();
+ let package = new_root_pkg("root/a");
+ package.set_autoload(autoload(vec![
+ (
+ "psr-0",
+ str_map(&[("Prefix", pstr("foo/bar/non/existing/"))]),
+ ),
+ (
+ "psr-4",
+ str_map(&[("Prefix\\", pstr("foo/bar/non/existing2/"))]),
+ ),
+ ]));
+
+ let vendor = s.vendor_dir.clone();
+ let composer_out = format!("{}/composer", vendor);
+ let class_map = dump(&mut s, package.into(), true, "_8").unwrap();
+ assert!(std::path::Path::new(&format!("{}/autoload_classmap.php", composer_out)).exists());
+
+ let mut expected: IndexMap<String, String> = IndexMap::new();
+ expected.insert(
+ "Composer\\InstalledVersions".to_string(),
+ format!("{}/composer/InstalledVersions.php", vendor),
+ );
+ assert_eq!(&expected, class_map.get_map());
}
#[test]
-#[ignore = "setUp needs mocked Config/InstallationManager/InstalledRepositoryInterface and TestCase tmp-dir helpers; no mock infra exists"]
+#[serial]
fn test_psr_to_class_map_ignores_non_psr_classes() {
- todo!()
+ let mut s = set_up();
+ let package = new_root_pkg("root/a");
+ package.set_autoload(autoload(vec![
+ ("psr-0", str_map(&[("psr0_", pstr("psr0/"))])),
+ ("psr-4", str_map(&[("psr4\\", pstr("psr4/"))])),
+ ]));
+
+ s.ensure_dir(&format!("{}/psr0/psr0", s.working_dir));
+ s.ensure_dir(&format!("{}/psr4", s.working_dir));
+ s.put(
+ &format!("{}/psr0/psr0/match.php", s.working_dir),
+ "<?php class psr0_match {}",
+ );
+ s.put(
+ &format!("{}/psr0/psr0/badfile.php", s.working_dir),
+ "<?php class psr0_badclass {}",
+ );
+ s.put(
+ &format!("{}/psr4/match.php", s.working_dir),
+ "<?php namespace psr4; class match {}",
+ );
+ s.put(
+ &format!("{}/psr4/badfile.php", s.working_dir),
+ "<?php namespace psr4; class badclass {}",
+ );
+
+ let vendor = s.vendor_dir.clone();
+ let composer_out = format!("{}/composer", vendor);
+ dump(&mut s, package.into(), true, "_1").unwrap();
+ assert!(std::path::Path::new(&format!("{}/autoload_classmap.php", composer_out)).exists());
+
+ let expected = format!(
+ "<?php\n\n// autoload_classmap.php @generated by Composer\n\n$vendorDir = dirname(__DIR__);\n$baseDir = dirname($vendorDir);\n\nreturn array(\n 'Composer\\\\InstalledVersions' => $vendorDir . '/composer/InstalledVersions.php',\n 'psr0_match' => $baseDir . '/psr0/psr0/match.php',\n 'psr4\\\\match' => $baseDir . '/psr4/match.php',\n);\n"
+ );
+ let actual =
+ std::fs::read_to_string(format!("{}/autoload_classmap.php", composer_out)).unwrap();
+ assert_eq!(expected, actual);
}
#[test]
-#[ignore = "setUp needs mocked Config/InstallationManager/InstalledRepositoryInterface and TestCase tmp-dir helpers; no mock infra exists"]
+#[serial]
fn test_vendors_class_map_autoloading() {
- todo!()
+ let mut s = set_up();
+ let package = new_root_pkg("root/a");
+ package.set_requires(requires(vec![
+ ("a/a", link("a", "a/a", match_all(), None)),
+ ("b/b", link("a", "b/b", match_all(), None)),
+ ]));
+
+ let a = new_pkg("a/a");
+ let b = new_pkg("b/b");
+ a.__set_autoload(autoload(vec![("classmap", str_list(&["src/"]))]));
+ b.__set_autoload(autoload(vec![("classmap", str_list(&["src/", "lib/"]))]));
+
+ s.set_canonical_packages(vec![a.into(), b.into()]);
+
+ s.ensure_dir(&format!("{}/composer", s.vendor_dir));
+ s.ensure_dir(&format!("{}/a/a/src", s.vendor_dir));
+ s.ensure_dir(&format!("{}/b/b/src", s.vendor_dir));
+ s.ensure_dir(&format!("{}/b/b/lib", s.vendor_dir));
+ s.put(
+ &format!("{}/a/a/src/a.php", s.vendor_dir),
+ "<?php class ClassMapFoo {}",
+ );
+ s.put(
+ &format!("{}/b/b/src/b.php", s.vendor_dir),
+ "<?php class ClassMapBar {}",
+ );
+ s.put(
+ &format!("{}/b/b/lib/c.php", s.vendor_dir),
+ "<?php class ClassMapBaz {}",
+ );
+
+ let vendor = s.vendor_dir.clone();
+ let composer_out = format!("{}/composer", vendor);
+ let class_map = dump(&mut s, package.into(), false, "_6").unwrap();
+
+ let mut expected: IndexMap<String, String> = IndexMap::new();
+ expected.insert(
+ "ClassMapBar".to_string(),
+ format!("{}/b/b/src/b.php", vendor),
+ );
+ expected.insert(
+ "ClassMapBaz".to_string(),
+ format!("{}/b/b/lib/c.php", vendor),
+ );
+ expected.insert(
+ "ClassMapFoo".to_string(),
+ format!("{}/a/a/src/a.php", vendor),
+ );
+ expected.insert(
+ "Composer\\InstalledVersions".to_string(),
+ format!("{}/composer/InstalledVersions.php", vendor),
+ );
+ assert_eq!(&expected, class_map.get_map());
+ assert_autoload_files("classmap4", &composer_out, "classmap");
}
#[test]
-#[ignore = "setUp needs mocked Config/InstallationManager/InstalledRepositoryInterface and TestCase tmp-dir helpers; no mock infra exists"]
+#[serial]
fn test_vendors_class_map_autoloading_with_target_dir() {
- todo!()
+ let mut s = set_up();
+ let package = new_root_pkg("root/a");
+ package.set_requires(requires(vec![
+ ("a/a", link("a", "a/a", match_all(), None)),
+ ("b/b", link("a", "b/b", match_all(), None)),
+ ]));
+
+ let a = new_pkg("a/a");
+ let b = new_pkg("b/b");
+ a.__set_autoload(autoload(vec![(
+ "classmap",
+ str_list(&["target/src/", "lib/"]),
+ )]));
+ a.__set_target_dir(Some("target".to_string()));
+ b.__set_autoload(autoload(vec![("classmap", str_list(&["src/"]))]));
+
+ s.set_canonical_packages(vec![a.into(), b.into()]);
+
+ s.ensure_dir(&format!("{}/composer", s.vendor_dir));
+ s.ensure_dir(&format!("{}/a/a/target/src", s.vendor_dir));
+ s.ensure_dir(&format!("{}/a/a/target/lib", s.vendor_dir));
+ s.ensure_dir(&format!("{}/b/b/src", s.vendor_dir));
+ s.put(
+ &format!("{}/a/a/target/src/a.php", s.vendor_dir),
+ "<?php class ClassMapFoo {}",
+ );
+ s.put(
+ &format!("{}/a/a/target/lib/b.php", s.vendor_dir),
+ "<?php class ClassMapBar {}",
+ );
+ s.put(
+ &format!("{}/b/b/src/c.php", s.vendor_dir),
+ "<?php class ClassMapBaz {}",
+ );
+
+ let vendor = s.vendor_dir.clone();
+ let composer_out = format!("{}/composer", vendor);
+ let class_map = dump(&mut s, package.into(), false, "_6").unwrap();
+ assert!(std::path::Path::new(&format!("{}/autoload_classmap.php", composer_out)).exists());
+
+ let mut expected: IndexMap<String, String> = IndexMap::new();
+ expected.insert(
+ "ClassMapBar".to_string(),
+ format!("{}/a/a/target/lib/b.php", vendor),
+ );
+ expected.insert(
+ "ClassMapBaz".to_string(),
+ format!("{}/b/b/src/c.php", vendor),
+ );
+ expected.insert(
+ "ClassMapFoo".to_string(),
+ format!("{}/a/a/target/src/a.php", vendor),
+ );
+ expected.insert(
+ "Composer\\InstalledVersions".to_string(),
+ format!("{}/composer/InstalledVersions.php", vendor),
+ );
+ assert_eq!(&expected, class_map.get_map());
}
#[test]
-#[ignore = "setUp needs mocked Config/InstallationManager/InstalledRepositoryInterface and TestCase tmp-dir helpers; no mock infra exists"]
+#[serial]
+#[ignore = "the `classmap => ['./']` rule yields a scanned path containing `/./` (e.g. c/c/./foo/test.php) that the ClassMapGenerator port does not collapse the way PHP's normalizePath does; needs production path-normalization fix"]
fn test_class_map_autoloading_empty_dir_and_exact_file() {
- todo!()
+ let mut s = set_up();
+ let package = new_root_pkg("root/a");
+ package.set_requires(requires(vec![
+ ("a/a", link("a", "a/a", match_all(), None)),
+ ("b/b", link("a", "b/b", match_all(), None)),
+ ("c/c", link("a", "c/c", match_all(), None)),
+ ]));
+
+ let a = new_pkg("a/a");
+ let b = new_pkg("b/b");
+ let c = new_pkg("c/c");
+ a.__set_autoload(autoload(vec![("classmap", str_list(&[""]))]));
+ b.__set_autoload(autoload(vec![("classmap", str_list(&["test.php"]))]));
+ c.__set_autoload(autoload(vec![("classmap", str_list(&["./"]))]));
+
+ s.set_canonical_packages(vec![a.into(), b.into(), c.into()]);
+
+ s.ensure_dir(&format!("{}/composer", s.vendor_dir));
+ s.ensure_dir(&format!("{}/a/a/src", s.vendor_dir));
+ s.ensure_dir(&format!("{}/b/b", s.vendor_dir));
+ s.ensure_dir(&format!("{}/c/c/foo", s.vendor_dir));
+ s.put(
+ &format!("{}/a/a/src/a.php", s.vendor_dir),
+ "<?php class ClassMapFoo {}",
+ );
+ s.put(
+ &format!("{}/b/b/test.php", s.vendor_dir),
+ "<?php class ClassMapBar {}",
+ );
+ s.put(
+ &format!("{}/c/c/foo/test.php", s.vendor_dir),
+ "<?php class ClassMapBaz {}",
+ );
+
+ let vendor = s.vendor_dir.clone();
+ let composer_out = format!("{}/composer", vendor);
+ let class_map = dump(&mut s, package.into(), false, "_7").unwrap();
+ assert!(std::path::Path::new(&format!("{}/autoload_classmap.php", composer_out)).exists());
+
+ let mut expected: IndexMap<String, String> = IndexMap::new();
+ expected.insert(
+ "ClassMapBar".to_string(),
+ format!("{}/b/b/test.php", vendor),
+ );
+ expected.insert(
+ "ClassMapBaz".to_string(),
+ format!("{}/c/c/foo/test.php", vendor),
+ );
+ expected.insert(
+ "ClassMapFoo".to_string(),
+ format!("{}/a/a/src/a.php", vendor),
+ );
+ expected.insert(
+ "Composer\\InstalledVersions".to_string(),
+ format!("{}/composer/InstalledVersions.php", vendor),
+ );
+ assert_eq!(&expected, class_map.get_map());
+ assert_autoload_files("classmap5", &composer_out, "classmap");
+
+ let real = std::fs::read_to_string(format!("{}/autoload_real.php", composer_out)).unwrap();
+ assert!(!real.contains("$loader->setClassMapAuthoritative(true);"));
+ assert!(!real.contains("$loader->setApcuPrefix("));
}
#[test]
-#[ignore = "setUp needs mocked Config/InstallationManager/InstalledRepositoryInterface and TestCase tmp-dir helpers; no mock infra exists"]
+#[serial]
fn test_class_map_autoloading_authoritative_and_apcu() {
- todo!()
+ let mut s = set_up();
+ let package = new_root_pkg("root/a");
+ package.set_requires(requires(vec![
+ ("a/a", link("a", "a/a", match_all(), None)),
+ ("b/b", link("a", "b/b", match_all(), None)),
+ ("c/c", link("a", "c/c", match_all(), None)),
+ ]));
+
+ let a = new_pkg("a/a");
+ let b = new_pkg("b/b");
+ let c = new_pkg("c/c");
+ a.__set_autoload(autoload(vec![("psr-4", str_map(&[("", pstr("src/"))]))]));
+ b.__set_autoload(autoload(vec![("psr-4", str_map(&[("", pstr("./"))]))]));
+ c.__set_autoload(autoload(vec![("psr-4", str_map(&[("", pstr("foo/"))]))]));
+
+ s.set_canonical_packages(vec![a.into(), b.into(), c.into()]);
+
+ s.ensure_dir(&format!("{}/composer", s.vendor_dir));
+ s.ensure_dir(&format!("{}/a/a/src", s.vendor_dir));
+ s.ensure_dir(&format!("{}/b/b", s.vendor_dir));
+ s.ensure_dir(&format!("{}/c/c/foo", s.vendor_dir));
+ s.put(
+ &format!("{}/a/a/src/ClassMapFoo.php", s.vendor_dir),
+ "<?php class ClassMapFoo {}",
+ );
+ s.put(
+ &format!("{}/b/b/ClassMapBar.php", s.vendor_dir),
+ "<?php class ClassMapBar {}",
+ );
+ s.put(
+ &format!("{}/c/c/foo/ClassMapBaz.php", s.vendor_dir),
+ "<?php class ClassMapBaz {}",
+ );
+
+ s.generator.set_class_map_authoritative(true);
+ s.generator.set_apcu(true, None);
+ let vendor = s.vendor_dir.clone();
+ let composer_out = format!("{}/composer", vendor);
+ let class_map = dump(&mut s, package.into(), false, "_7").unwrap();
+ assert!(std::path::Path::new(&format!("{}/autoload_classmap.php", composer_out)).exists());
+
+ let mut expected: IndexMap<String, String> = IndexMap::new();
+ expected.insert(
+ "ClassMapBar".to_string(),
+ format!("{}/b/b/ClassMapBar.php", vendor),
+ );
+ expected.insert(
+ "ClassMapBaz".to_string(),
+ format!("{}/c/c/foo/ClassMapBaz.php", vendor),
+ );
+ expected.insert(
+ "ClassMapFoo".to_string(),
+ format!("{}/a/a/src/ClassMapFoo.php", vendor),
+ );
+ expected.insert(
+ "Composer\\InstalledVersions".to_string(),
+ format!("{}/composer/InstalledVersions.php", vendor),
+ );
+ assert_eq!(&expected, class_map.get_map());
+ assert_autoload_files("classmap8", &composer_out, "classmap");
+
+ let real = std::fs::read_to_string(format!("{}/autoload_real.php", composer_out)).unwrap();
+ assert!(real.contains("$loader->setClassMapAuthoritative(true);"));
+ assert!(real.contains("$loader->setApcuPrefix("));
}
#[test]
-#[ignore = "setUp needs mocked Config/InstallationManager/InstalledRepositoryInterface and TestCase tmp-dir helpers; no mock infra exists"]
+#[serial]
fn test_class_map_autoloading_authoritative_and_apcu_prefix() {
- todo!()
+ let mut s = set_up();
+ let package = new_root_pkg("root/a");
+ package.set_requires(requires(vec![
+ ("a/a", link("a", "a/a", match_all(), None)),
+ ("b/b", link("a", "b/b", match_all(), None)),
+ ("c/c", link("a", "c/c", match_all(), None)),
+ ]));
+
+ let a = new_pkg("a/a");
+ let b = new_pkg("b/b");
+ let c = new_pkg("c/c");
+ a.__set_autoload(autoload(vec![("psr-4", str_map(&[("", pstr("src/"))]))]));
+ b.__set_autoload(autoload(vec![("psr-4", str_map(&[("", pstr("./"))]))]));
+ c.__set_autoload(autoload(vec![("psr-4", str_map(&[("", pstr("foo/"))]))]));
+
+ s.set_canonical_packages(vec![a.into(), b.into(), c.into()]);
+
+ s.ensure_dir(&format!("{}/composer", s.vendor_dir));
+ s.ensure_dir(&format!("{}/a/a/src", s.vendor_dir));
+ s.ensure_dir(&format!("{}/b/b", s.vendor_dir));
+ s.ensure_dir(&format!("{}/c/c/foo", s.vendor_dir));
+ s.put(
+ &format!("{}/a/a/src/ClassMapFoo.php", s.vendor_dir),
+ "<?php class ClassMapFoo {}",
+ );
+ s.put(
+ &format!("{}/b/b/ClassMapBar.php", s.vendor_dir),
+ "<?php class ClassMapBar {}",
+ );
+ s.put(
+ &format!("{}/c/c/foo/ClassMapBaz.php", s.vendor_dir),
+ "<?php class ClassMapBaz {}",
+ );
+
+ s.generator.set_class_map_authoritative(true);
+ s.generator
+ .set_apcu(true, Some("custom'Prefix".to_string()));
+ let vendor = s.vendor_dir.clone();
+ let composer_out = format!("{}/composer", vendor);
+ let class_map = dump(&mut s, package.into(), false, "_7").unwrap();
+ assert!(std::path::Path::new(&format!("{}/autoload_classmap.php", composer_out)).exists());
+
+ let mut expected: IndexMap<String, String> = IndexMap::new();
+ expected.insert(
+ "ClassMapBar".to_string(),
+ format!("{}/b/b/ClassMapBar.php", vendor),
+ );
+ expected.insert(
+ "ClassMapBaz".to_string(),
+ format!("{}/c/c/foo/ClassMapBaz.php", vendor),
+ );
+ expected.insert(
+ "ClassMapFoo".to_string(),
+ format!("{}/a/a/src/ClassMapFoo.php", vendor),
+ );
+ expected.insert(
+ "Composer\\InstalledVersions".to_string(),
+ format!("{}/composer/InstalledVersions.php", vendor),
+ );
+ assert_eq!(&expected, class_map.get_map());
+ assert_autoload_files("classmap8", &composer_out, "classmap");
+
+ let real = std::fs::read_to_string(format!("{}/autoload_real.php", composer_out)).unwrap();
+ assert!(real.contains("$loader->setClassMapAuthoritative(true);"));
+ assert!(real.contains("$loader->setApcuPrefix('custom\\'Prefix');"));
}
#[test]
-#[ignore = "setUp needs mocked Config/InstallationManager/InstalledRepositoryInterface and TestCase tmp-dir helpers; no mock infra exists"]
+#[serial]
+#[ignore = "autoload_real.php/autoload_static.php fixtures track a newer Composer template (single blank lines + $filesToLoad/$requireFile block) than the current AutoloadGenerator port emits; needs production template alignment"]
fn test_files_autoload_generation() {
- todo!()
+ let mut s = set_up();
+ let package = new_root_pkg("root/a");
+ package.set_autoload(autoload(vec![("files", str_list(&["root.php"]))]));
+ package.set_requires(requires(vec![
+ ("a/a", link("a", "a/a", match_all(), None)),
+ ("b/b", link("a", "b/b", match_all(), None)),
+ ("c/c", link("a", "c/c", match_all(), None)),
+ ]));
+
+ let a = new_pkg("a/a");
+ let b = new_pkg("b/b");
+ let c = new_pkg("c/c");
+ a.__set_autoload(autoload(vec![("files", str_list(&["test.php"]))]));
+ b.__set_autoload(autoload(vec![("files", str_list(&["test2.php"]))]));
+ c.__set_autoload(autoload(vec![(
+ "files",
+ str_list(&["test3.php", "foo/bar/test4.php"]),
+ )]));
+ c.__set_target_dir(Some("foo/bar".to_string()));
+
+ s.set_canonical_packages(vec![a.into(), b.into(), c.into()]);
+
+ s.ensure_dir(&format!("{}/a/a", s.vendor_dir));
+ s.ensure_dir(&format!("{}/b/b", s.vendor_dir));
+ s.ensure_dir(&format!("{}/c/c/foo/bar", s.vendor_dir));
+ s.put(
+ &format!("{}/a/a/test.php", s.vendor_dir),
+ "<?php function testFilesAutoloadGeneration1() {}",
+ );
+ s.put(
+ &format!("{}/b/b/test2.php", s.vendor_dir),
+ "<?php function testFilesAutoloadGeneration2() {}",
+ );
+ s.put(
+ &format!("{}/c/c/foo/bar/test3.php", s.vendor_dir),
+ "<?php function testFilesAutoloadGeneration3() {}",
+ );
+ s.put(
+ &format!("{}/c/c/foo/bar/test4.php", s.vendor_dir),
+ "<?php function testFilesAutoloadGeneration4() {}",
+ );
+ s.put(
+ &format!("{}/root.php", s.working_dir),
+ "<?php function testFilesAutoloadGenerationRoot() {}",
+ );
+
+ let vendor = s.vendor_dir.clone();
+ let composer_out = format!("{}/composer", vendor);
+ dump(&mut s, package.into(), false, "FilesAutoload").unwrap();
+ let fx = fixtures_dir();
+ assert_file_content_equals(
+ fx.join("autoload_functions.php").to_str().unwrap(),
+ &format!("{}/autoload.php", vendor),
+ );
+ assert_file_content_equals(
+ fx.join("autoload_real_functions.php").to_str().unwrap(),
+ &format!("{}/autoload_real.php", composer_out),
+ );
+ assert_file_content_equals(
+ fx.join("autoload_static_functions.php").to_str().unwrap(),
+ &format!("{}/autoload_static.php", composer_out),
+ );
+ assert_file_content_equals(
+ fx.join("autoload_files_functions.php").to_str().unwrap(),
+ &format!("{}/autoload_files.php", composer_out),
+ );
}
#[test]
-#[ignore = "setUp needs mocked Config/InstallationManager/InstalledRepositoryInterface and TestCase tmp-dir helpers; no mock infra exists"]
-fn test_files_autoload_generation_remove_extra_entities_from_autoload_files() {
- todo!()
+#[serial]
+fn test_override_vendors_autoloading() {
+ let mut s = set_up();
+ let working_dir = s.working_dir.clone();
+ let root_package = new_root_pkg("root/z");
+ root_package.set_autoload(autoload(vec![
+ (
+ "psr-0",
+ str_map(&[("A\\B", pstr(&format!("{}/lib", working_dir)))]),
+ ),
+ ("classmap", str_list(&[&format!("{}/src", working_dir)])),
+ ]));
+ root_package.set_requires(requires(vec![
+ ("a/a", link("z", "a/a", match_all(), None)),
+ ("b/b", link("z", "b/b", match_all(), None)),
+ ]));
+
+ let a = new_pkg("a/a");
+ let b = new_pkg("b/b");
+ a.__set_autoload(autoload(vec![
+ (
+ "psr-0",
+ str_map(&[("A", pstr("src/")), ("A\\B", pstr("lib/"))]),
+ ),
+ ("classmap", str_list(&["classmap"])),
+ ]));
+ b.__set_autoload(autoload(vec![(
+ "psr-0",
+ str_map(&[("B\\Sub\\Name", pstr("src/"))]),
+ )]));
+
+ s.set_canonical_packages(vec![a.into(), b.into()]);
+
+ s.ensure_dir(&format!("{}/lib/A/B", s.working_dir));
+ s.ensure_dir(&format!("{}/src/", s.working_dir));
+ s.ensure_dir(&format!("{}/composer", s.vendor_dir));
+ s.ensure_dir(&format!("{}/a/a/classmap", s.vendor_dir));
+ s.ensure_dir(&format!("{}/a/a/src", s.vendor_dir));
+ s.ensure_dir(&format!("{}/a/a/lib/A/B", s.vendor_dir));
+ s.ensure_dir(&format!("{}/b/b/src", s.vendor_dir));
+
+ s.put(
+ &format!("{}/lib/A/B/C.php", s.working_dir),
+ "<?php namespace A\\B; class C {}",
+ );
+ s.put(
+ &format!("{}/src/classes.php", s.working_dir),
+ "<?php namespace Foo; class Bar {}",
+ );
+ s.put(
+ &format!("{}/a/a/lib/A/B/C.php", s.vendor_dir),
+ "<?php namespace A\\B; class C {}",
+ );
+ s.put(
+ &format!("{}/a/a/classmap/classes.php", s.vendor_dir),
+ "<?php namespace Foo; class Bar {}",
+ );
+
+ let composer_out = format!("{}/composer", s.vendor_dir);
+ dump(&mut s, root_package.into(), true, "_9").unwrap();
+
+ let expected_namespace = "<?php\n\n// autoload_namespaces.php @generated by Composer\n\n$vendorDir = dirname(__DIR__);\n$baseDir = dirname($vendorDir);\n\nreturn array(\n 'B\\\\Sub\\\\Name' => array($vendorDir . '/b/b/src'),\n 'A\\\\B' => array($baseDir . '/lib', $vendorDir . '/a/a/lib'),\n 'A' => array($vendorDir . '/a/a/src'),\n);\n";
+ let expected_psr4 = "<?php\n\n// autoload_psr4.php @generated by Composer\n\n$vendorDir = dirname(__DIR__);\n$baseDir = dirname($vendorDir);\n\nreturn array(\n);\n";
+ let expected_classmap = "<?php\n\n// autoload_classmap.php @generated by Composer\n\n$vendorDir = dirname(__DIR__);\n$baseDir = dirname($vendorDir);\n\nreturn array(\n 'A\\\\B\\\\C' => $baseDir . '/lib/A/B/C.php',\n 'Composer\\\\InstalledVersions' => $vendorDir . '/composer/InstalledVersions.php',\n 'Foo\\\\Bar' => $baseDir . '/src/classes.php',\n);\n";
+
+ assert_eq!(
+ expected_namespace,
+ std::fs::read_to_string(format!("{}/autoload_namespaces.php", composer_out)).unwrap()
+ );
+ assert_eq!(
+ expected_psr4,
+ std::fs::read_to_string(format!("{}/autoload_psr4.php", composer_out)).unwrap()
+ );
+ assert_eq!(
+ expected_classmap,
+ std::fs::read_to_string(format!("{}/autoload_classmap.php", composer_out)).unwrap()
+ );
}
#[test]
-#[ignore = "setUp needs mocked Config/InstallationManager/InstalledRepositoryInterface and TestCase tmp-dir helpers; no mock infra exists"]
-fn test_files_autoload_order_by_dependencies() {
- todo!()
+#[serial]
+fn test_include_path_file_generation() {
+ let mut s = set_up();
+ let package = new_root_pkg("root/a");
+
+ let a = new_pkg("a/a");
+ a.__set_include_paths(vec!["lib/".to_string()]);
+ let b = new_pkg("b/b");
+ b.__set_include_paths(vec!["library".to_string()]);
+ let c = new_pkg("c");
+ c.__set_include_paths(vec!["library".to_string()]);
+
+ s.set_canonical_packages(vec![a.into(), b.into(), c.into()]);
+
+ s.ensure_dir(&format!("{}/composer", s.vendor_dir));
+
+ let composer_out = format!("{}/composer", s.vendor_dir);
+ dump(&mut s, package.into(), false, "_10").unwrap();
+
+ assert_file_content_equals(
+ fixtures_dir().join("include_paths.php").to_str().unwrap(),
+ &format!("{}/include_paths.php", composer_out),
+ );
}
#[test]
-#[ignore = "setUp needs mocked Config/InstallationManager/InstalledRepositoryInterface and TestCase tmp-dir helpers; no mock infra exists"]
-fn test_override_vendors_autoloading() {
- todo!()
+#[serial]
+fn test_include_path_file_without_paths_is_skipped() {
+ let mut s = set_up();
+ let package = new_root_pkg("root/a");
+ let a = new_pkg("a/a");
+ s.set_canonical_packages(vec![a.into()]);
+
+ s.ensure_dir(&format!("{}/composer", s.vendor_dir));
+
+ let composer_out = format!("{}/composer", s.vendor_dir);
+ dump(&mut s, package.into(), false, "_12").unwrap();
+
+ assert!(!std::path::Path::new(&format!("{}/include_paths.php", composer_out)).exists());
}
#[test]
-#[ignore = "setUp needs mocked Config/InstallationManager/InstalledRepositoryInterface and TestCase tmp-dir helpers; no mock infra exists"]
-fn test_include_path_file_generation() {
- todo!()
+#[serial]
+fn test_vendor_substring_path() {
+ let mut s = set_up();
+ let package = new_root_pkg("root/a");
+ package.set_autoload(autoload(vec![
+ (
+ "psr-0",
+ str_map(&[("Foo", pstr("composer-test-autoload-src/src"))]),
+ ),
+ (
+ "psr-4",
+ str_map(&[("Acme\\Foo\\", pstr("composer-test-autoload-src/src-psr4"))]),
+ ),
+ ]));
+
+ s.ensure_dir(&format!("{}/a", s.vendor_dir));
+
+ let composer_out = format!("{}/composer", s.vendor_dir);
+ dump(&mut s, package.into(), false, "VendorSubstring").unwrap();
+
+ let expected_namespace = "<?php\n\n// autoload_namespaces.php @generated by Composer\n\n$vendorDir = dirname(__DIR__);\n$baseDir = dirname($vendorDir);\n\nreturn array(\n 'Foo' => array($baseDir . '/composer-test-autoload-src/src'),\n);\n";
+ let expected_psr4 = "<?php\n\n// autoload_psr4.php @generated by Composer\n\n$vendorDir = dirname(__DIR__);\n$baseDir = dirname($vendorDir);\n\nreturn array(\n 'Acme\\\\Foo\\\\' => array($baseDir . '/composer-test-autoload-src/src-psr4'),\n);\n";
+
+ assert_eq!(
+ expected_namespace,
+ std::fs::read_to_string(format!("{}/autoload_namespaces.php", composer_out)).unwrap()
+ );
+ assert_eq!(
+ expected_psr4,
+ std::fs::read_to_string(format!("{}/autoload_psr4.php", composer_out)).unwrap()
+ );
}
#[test]
-#[ignore = "setUp needs mocked Config/InstallationManager/InstalledRepositoryInterface and TestCase tmp-dir helpers; no mock infra exists"]
-fn test_include_paths_are_prepended_in_autoload_file() {
- todo!()
+#[serial]
+fn test_empty_paths() {
+ let mut s = set_up();
+ let package = new_root_pkg("root/a");
+ package.set_autoload(autoload(vec![
+ ("psr-0", str_map(&[("Foo", pstr(""))])),
+ ("psr-4", str_map(&[("Acme\\Foo\\", pstr(""))])),
+ ("classmap", str_list(&[""])),
+ ]));
+
+ s.ensure_dir(&format!("{}/Foo", s.working_dir));
+ s.put(
+ &format!("{}/Foo/Bar.php", s.working_dir),
+ "<?php namespace Foo; class Bar {}",
+ );
+ s.put(
+ &format!("{}/class.php", s.working_dir),
+ "<?php namespace Classmap; class Foo {}",
+ );
+
+ let composer_out = format!("{}/composer", s.vendor_dir);
+ dump(&mut s, package.into(), true, "_15").unwrap();
+
+ let expected_namespace = "<?php\n\n// autoload_namespaces.php @generated by Composer\n\n$vendorDir = dirname(__DIR__);\n$baseDir = dirname($vendorDir);\n\nreturn array(\n 'Foo' => array($baseDir . '/'),\n);\n";
+ let expected_psr4 = "<?php\n\n// autoload_psr4.php @generated by Composer\n\n$vendorDir = dirname(__DIR__);\n$baseDir = dirname($vendorDir);\n\nreturn array(\n 'Acme\\\\Foo\\\\' => array($baseDir . '/'),\n);\n";
+ let expected_classmap = "<?php\n\n// autoload_classmap.php @generated by Composer\n\n$vendorDir = dirname(__DIR__);\n$baseDir = dirname($vendorDir);\n\nreturn array(\n 'Classmap\\\\Foo' => $baseDir . '/class.php',\n 'Composer\\\\InstalledVersions' => $vendorDir . '/composer/InstalledVersions.php',\n 'Foo\\\\Bar' => $baseDir . '/Foo/Bar.php',\n);\n";
+
+ assert_eq!(
+ expected_namespace,
+ std::fs::read_to_string(format!("{}/autoload_namespaces.php", composer_out)).unwrap()
+ );
+ assert_eq!(
+ expected_psr4,
+ std::fs::read_to_string(format!("{}/autoload_psr4.php", composer_out)).unwrap()
+ );
+ assert_eq!(
+ expected_classmap,
+ std::fs::read_to_string(format!("{}/autoload_classmap.php", composer_out)).unwrap()
+ );
}
#[test]
-#[ignore = "setUp needs mocked Config/InstallationManager/InstalledRepositoryInterface and TestCase tmp-dir helpers; no mock infra exists"]
-fn test_include_paths_in_root_package() {
- todo!()
+#[serial]
+#[ignore = "fixture assumes the symlinked composersrc/foo/bar tree (created via `ln -s` in PHP) and exercises exclude-from-classmap pattern matching that the port does not yet apply; symlink setup not replicated"]
+fn test_exclude_from_classmap() {
+ let mut s = set_up();
+ let package = new_root_pkg("root/a");
+ package.set_autoload(autoload(vec![
+ (
+ "psr-0",
+ str_map(&[
+ ("Main", pstr("src/")),
+ ("Lala", str_list(&["src/", "lib/"])),
+ ]),
+ ),
+ (
+ "psr-4",
+ str_map(&[
+ ("Acme\\Fruit\\", pstr("src-fruit/")),
+ ("Acme\\Cake\\", str_list(&["src-cake/", "lib-cake/"])),
+ ]),
+ ),
+ ("classmap", str_list(&["composersrc/"])),
+ (
+ "exclude-from-classmap",
+ str_list(&[
+ "/composersrc/foo/bar/",
+ "/composersrc/excludedTests/",
+ "/composersrc/ClassToExclude.php",
+ "/composersrc/*/excluded/excsubpath",
+ "**/excsubpath",
+ "composers",
+ "/src-ca/",
+ ]),
+ ),
+ ]));
+
+ s.ensure_dir(&format!("{}/composer", s.working_dir));
+ s.ensure_dir(&format!("{}/src/Lala/Test", s.working_dir));
+ s.ensure_dir(&format!("{}/lib", s.working_dir));
+ s.put(
+ &format!("{}/src/Lala/ClassMapMain.php", s.working_dir),
+ "<?php namespace Lala; class ClassMapMain {}",
+ );
+ s.put(
+ &format!("{}/src/Lala/Test/ClassMapMainTest.php", s.working_dir),
+ "<?php namespace Lala\\Test; class ClassMapMainTest {}",
+ );
+
+ s.ensure_dir(&format!("{}/src-fruit", s.working_dir));
+ s.ensure_dir(&format!("{}/src-cake", s.working_dir));
+ s.ensure_dir(&format!("{}/lib-cake", s.working_dir));
+ s.put(
+ &format!("{}/src-cake/ClassMapBar.php", s.working_dir),
+ "<?php namespace Acme\\Cake; class ClassMapBar {}",
+ );
+
+ s.ensure_dir(&format!("{}/composersrc", s.working_dir));
+ s.ensure_dir(&format!("{}/composersrc/tests", s.working_dir));
+ s.put(
+ &format!("{}/composersrc/foo.php", s.working_dir),
+ "<?php class ClassMapFoo {}",
+ );
+
+ s.ensure_dir(&format!("{}/composersrc/excludedTests", s.working_dir));
+ s.put(
+ &format!("{}/composersrc/excludedTests/bar.php", s.working_dir),
+ "<?php class ClassExcludeMapFoo {}",
+ );
+ s.put(
+ &format!("{}/composersrc/ClassToExclude.php", s.working_dir),
+ "<?php class ClassClassToExclude {}",
+ );
+ s.ensure_dir(&format!(
+ "{}/composersrc/long/excluded/excsubpath",
+ s.working_dir
+ ));
+ s.put(
+ &format!(
+ "{}/composersrc/long/excluded/excsubpath/foo.php",
+ s.working_dir
+ ),
+ "<?php class ClassExcludeMapFoo2 {}",
+ );
+ s.put(
+ &format!(
+ "{}/composersrc/long/excluded/excsubpath/bar.php",
+ s.working_dir
+ ),
+ "<?php class ClassExcludeMapBar {}",
+ );
+
+ s.ensure_dir(&format!("{}/composersrc/foo", s.working_dir));
+
+ let composer_out = format!("{}/composer", s.vendor_dir);
+ dump(&mut s, package.into(), true, "_1").unwrap();
+
+ assert_autoload_files("classmap", &composer_out, "classmap");
}
+// These remain ignored: they need test infrastructure not yet ported.
+//
+// - testFilesAutoloadOrderByDependencies / testFilesAutoloadGeneration's `require autoload.php`
+// + function_exists assertions: PHP runtime require is unportable (composer_require todo!()).
+// - testFilesAutoloadGenerationRemoveExtraEntitiesFromAutoloadFiles: needs getCanonicalPackages
+// returnValueMap over consecutive calls (the repo mock yields different package sets per call).
+// - testIncludePathsArePrependedInAutoloadFile / testIncludePathsInRootPackage /
+// testUseGlobalIncludePath: assert PHP's get_include_path() after `require autoload.php`.
+// - testPreAndPostEventsAreDispatchedDuringAutoloadDump: EventDispatcher::dispatchScript spy.
+// - testVendorDirExcludedFromWorkingDir / testUpLevelRelativePaths: chdir into a nested working dir
+// with a custom getInstallPath using a different vendor dir.
+// - testAutoloadRulesInPackageThatDoesNotExistOnDisk: exercises buildPackageMap/parseAutoloads
+// directly plus a CompletePackage; multi-dump with mutation.
+// - testGeneratesPlatformCheck: data-provider over many platform-requirement scenarios.
+// - testAbsoluteSymlinkWith*: create real filesystem symlinks.
+
#[test]
-#[ignore = "setUp needs mocked Config/InstallationManager/InstalledRepositoryInterface and TestCase tmp-dir helpers; no mock infra exists"]
-fn test_include_path_file_without_paths_is_skipped() {
+#[ignore = "require autoload.php + function_exists() assertions are unportable (composer_require todo!())"]
+fn test_files_autoload_order_by_dependencies() {
todo!()
}
#[test]
-#[ignore = "setUp needs mocked Config/InstallationManager/InstalledRepositoryInterface plus EventDispatcher::dispatch expectations; no mock infra exists"]
-fn test_pre_and_post_events_are_dispatched_during_autoload_dump() {
+#[ignore = "needs getCanonicalPackages consecutive-call return values (different package set per dump)"]
+fn test_files_autoload_generation_remove_extra_entities_from_autoload_files() {
todo!()
}
#[test]
-#[ignore = "setUp needs mocked Config/InstallationManager/InstalledRepositoryInterface and TestCase tmp-dir helpers; no mock infra exists"]
-fn test_use_global_include_path() {
+#[ignore = "asserts PHP get_include_path() after require autoload.php"]
+fn test_include_paths_are_prepended_in_autoload_file() {
todo!()
}
#[test]
-#[ignore = "setUp needs mocked Config/InstallationManager/InstalledRepositoryInterface and TestCase tmp-dir helpers; no mock infra exists"]
-fn test_vendor_dir_excluded_from_working_dir() {
+#[ignore = "asserts PHP get_include_path() after require autoload.php"]
+fn test_include_paths_in_root_package() {
todo!()
}
#[test]
-#[ignore = "setUp needs mocked Config/InstallationManager/InstalledRepositoryInterface and TestCase tmp-dir helpers; no mock infra exists"]
-fn test_up_level_relative_paths() {
+#[ignore = "EventDispatcher::dispatchScript spy not modeled"]
+fn test_pre_and_post_events_are_dispatched_during_autoload_dump() {
todo!()
}
#[test]
-#[ignore = "setUp needs mocked Config/InstallationManager/InstalledRepositoryInterface and TestCase tmp-dir helpers; no mock infra exists"]
-fn test_autoload_rules_in_package_that_does_not_exist_on_disk() {
+#[ignore = "asserts PHP get_include_path()/require behavior with use-include-path"]
+fn test_use_global_include_path() {
todo!()
}
#[test]
-#[ignore = "setUp needs mocked Config/InstallationManager/InstalledRepositoryInterface and TestCase tmp-dir helpers; no mock infra exists"]
-fn test_empty_paths() {
+#[ignore = "needs nested working dir + custom getInstallPath vendor dir"]
+fn test_vendor_dir_excluded_from_working_dir() {
todo!()
}
#[test]
-#[ignore = "setUp needs mocked Config/InstallationManager/InstalledRepositoryInterface and TestCase tmp-dir helpers; no mock infra exists"]
-fn test_vendor_substring_path() {
+#[ignore = "needs nested working dir chdir + up-level relative path fixtures"]
+fn test_up_level_relative_paths() {
todo!()
}
#[test]
-#[ignore = "setUp needs mocked Config/InstallationManager/InstalledRepositoryInterface and TestCase tmp-dir helpers; no mock infra exists"]
-fn test_exclude_from_classmap() {
+#[ignore = "exercises buildPackageMap/parseAutoloads directly with multi-dump mutation"]
+fn test_autoload_rules_in_package_that_does_not_exist_on_disk() {
todo!()
}
#[test]
-#[ignore = "setUp needs mocked Config/InstallationManager/InstalledRepositoryInterface and TestCase tmp-dir helpers; no mock infra exists"]
+#[ignore = "data-provider over platform-requirement scenarios"]
fn test_generates_platform_check() {
todo!()
}
#[test]
-#[ignore = "setUp needs mocked Config/InstallationManager/InstalledRepositoryInterface and TestCase tmp-dir helpers; no mock infra exists"]
+#[ignore = "creates real filesystem symlinks"]
fn test_absolute_symlink_with_psr4_does_not_generate_warnings() {
todo!()
}
#[test]
-#[ignore = "setUp needs mocked Config/InstallationManager/InstalledRepositoryInterface and TestCase tmp-dir helpers; no mock infra exists"]
+#[ignore = "creates real filesystem symlinks"]
fn test_absolute_symlink_with_classmap_exclude_from_classmap() {
todo!()
}
diff --git a/crates/shirabe/tests/autoload/main.rs b/crates/shirabe/tests/autoload/main.rs
index ebfa8e3..cc67e9a 100644
--- a/crates/shirabe/tests/autoload/main.rs
+++ b/crates/shirabe/tests/autoload/main.rs
@@ -1,2 +1,5 @@
+#[path = "../common/config_stub.rs"]
+mod config_stub;
+
mod autoload_generator_test;
mod class_loader_test;
diff --git a/crates/shirabe/tests/command/bump_command_test.rs b/crates/shirabe/tests/command/bump_command_test.rs
index 4e9a49c..8866990 100644
--- a/crates/shirabe/tests/command/bump_command_test.rs
+++ b/crates/shirabe/tests/command/bump_command_test.rs
@@ -1,19 +1,79 @@
//! ref: composer/tests/Composer/Test/Command/BumpCommandTest.php
-#[ignore = "missing TestCase::init_temp_composer, create_installed_json, create_composer_lock, and get_application_tester (ApplicationTester) infrastructure"]
+use crate::test_case::{RunOptions, get_application_tester, init_temp_composer};
+use serial_test::serial;
+use shirabe_php_shim::PhpMixed;
+
+#[ignore = "missing TestCase::create_installed_json / create_composer_lock infrastructure and full bump flow (require_composer reaches the network)"]
#[test]
fn test_bump() {
todo!()
}
-#[ignore = "missing TestCase::init_temp_composer and get_application_tester (ApplicationTester) infrastructure"]
#[test]
+#[serial]
fn test_bump_fails_on_non_existing_composer_file() {
- todo!()
+ let tear_down = init_temp_composer(Some(&serde_json::json!({})), None, None, false);
+ let composer_json_path = tear_down.working_dir().join("composer.json");
+ std::fs::remove_file(&composer_json_path).unwrap();
+
+ let mut app_tester = get_application_tester();
+ let status_code = app_tester
+ .run(
+ vec![(PhpMixed::from("command"), PhpMixed::from("bump"))],
+ RunOptions {
+ capture_stderr_separately: true,
+ ..RunOptions::default()
+ },
+ )
+ .unwrap();
+
+ assert_eq!(1, status_code);
+ let error_output = app_tester.get_error_output();
+ assert!(
+ error_output.contains("./composer.json is not readable."),
+ "expected error output to mention composer.json not readable, got: {:?}",
+ error_output,
+ );
+
+ drop(tear_down);
}
-#[ignore = "missing TestCase::init_temp_composer and get_application_tester (ApplicationTester) infrastructure"]
#[test]
+#[serial]
+#[ignore = "BumpCommand::initialize constructs a full Composer (Factory::create_http_downloader \
+ -> CurlDownloader::new -> curl_multi_init), and the curl subsystem in shirabe-php-shim \
+ is still todo!(). Only reachable once the HTTP/curl layer is ported. The companion \
+ test_bump_fails_on_non_existing_composer_file covers the same error-output capture path \
+ without reaching curl (the missing composer.json makes Composer construction a no-op)."]
fn test_bump_fails_on_write_error_to_composer_file() {
- todo!()
+ if shirabe_php_shim::function_exists("posix_getuid") && shirabe_php_shim::posix_getuid() == 0 {
+ // ref: $this->markTestSkipped('Cannot run as root');
+ return;
+ }
+
+ let tear_down = init_temp_composer(Some(&serde_json::json!({})), None, None, false);
+ let composer_json_path = tear_down.working_dir().join("composer.json");
+ shirabe_php_shim::chmod(&composer_json_path.to_string_lossy(), 0o444);
+
+ let mut app_tester = get_application_tester();
+ let status_code = app_tester
+ .run(
+ vec![(PhpMixed::from("command"), PhpMixed::from("bump"))],
+ RunOptions {
+ capture_stderr_separately: true,
+ ..RunOptions::default()
+ },
+ )
+ .unwrap();
+
+ assert_eq!(1, status_code);
+ let error_output = app_tester.get_error_output();
+ assert!(
+ error_output.contains("./composer.json is not writable."),
+ "expected error output to mention composer.json not writable, got: {:?}",
+ error_output,
+ );
+
+ drop(tear_down);
}
diff --git a/crates/shirabe/tests/installer/main.rs b/crates/shirabe/tests/installer/main.rs
index 931e70b..a5ca671 100644
--- a/crates/shirabe/tests/installer/main.rs
+++ b/crates/shirabe/tests/installer/main.rs
@@ -1,5 +1,7 @@
#[path = "../common/downloader_stub.rs"]
mod downloader_stub;
+#[path = "../common/io_mock.rs"]
+mod io_mock;
#[path = "../common/test_case.rs"]
mod test_case;
diff --git a/crates/shirabe/tests/installer/suggested_packages_reporter_test.rs b/crates/shirabe/tests/installer/suggested_packages_reporter_test.rs
index b0654fe..fa3c8fb 100644
--- a/crates/shirabe/tests/installer/suggested_packages_reporter_test.rs
+++ b/crates/shirabe/tests/installer/suggested_packages_reporter_test.rs
@@ -6,12 +6,22 @@ use std::rc::Rc;
use indexmap::IndexMap;
use shirabe::installer::SuggestedPackagesReporter;
use shirabe::io::IOInterface;
+use shirabe::io::io_interface;
use shirabe::io::null_io::NullIO;
+use shirabe::repository::{InstalledRepository, LockArrayRepository, RepositoryInterfaceHandle};
-/// Builds an IO mock and a SuggestedPackagesReporter over it. The IO mock
-/// (`getIOMock`) is not available here, so this remains a stub.
-fn set_up() {
- todo!()
+use crate::io_mock::{Expectation, IOMock, IOMockGuard, get_io_mock};
+use crate::test_case::get_package;
+
+/// ref: SuggestedPackagesReporterTest::setUp.
+///
+/// Builds an IO mock and a SuggestedPackagesReporter sharing it. The IOMockGuard runs
+/// assert_complete when it drops at the end of the test scope.
+fn set_up() -> (Rc<RefCell<IOMock>>, SuggestedPackagesReporter, IOMockGuard) {
+ let (mock, guard) = get_io_mock(io_interface::NORMAL).unwrap();
+ let io: Rc<RefCell<dyn IOInterface>> = mock.clone();
+ let reporter = SuggestedPackagesReporter::new(io);
+ (mock, reporter, guard)
}
/// ref: SuggestedPackagesReporterTest::getSuggestedPackageArray
@@ -28,12 +38,17 @@ fn reporter() -> SuggestedPackagesReporter {
SuggestedPackagesReporter::new(io)
}
-// These construct a SuggestedPackagesReporter with a mocked IO and assert its accumulated
-// suggestions and formatted output; mocking is not available here.
-#[ignore = "asserts IO mock output via getIOMock()->expects(); no IO mocking infrastructure exists and BufferIO::new/get_output are todo!()"]
#[test]
fn test_constructor() {
- todo!()
+ let (mock, mut reporter, _guard) = set_up();
+ mock.borrow_mut()
+ .expects(vec![Expectation::text("b")], true)
+ .unwrap();
+
+ reporter.add_package("a".to_string(), "b".to_string(), "c".to_string());
+ reporter
+ .output(SuggestedPackagesReporter::MODE_LIST, None, None)
+ .unwrap();
}
#[test]
@@ -77,44 +92,185 @@ fn test_add_package_appends() {
);
}
-#[ignore = "addSuggestionsFromPackage test mocks Package::getSuggests; set_suggests only exists on RootPackageHandle, so the non-root Package fixture with suggests cannot be expressed"]
#[test]
fn test_add_suggestions_from_package() {
- todo!()
+ let mut reporter = reporter();
+
+ // PHP mocks getSuggests/getPrettyName; here a real package carries the suggests and name.
+ let package = get_package("package-pretty-name", "1.0.0");
+ let mut suggests = IndexMap::new();
+ suggests.insert("target-a".to_string(), "reason-a".to_string());
+ suggests.insert("target-b".to_string(), "reason-b".to_string());
+ package.__set_suggests(suggests);
+
+ reporter.add_suggestions_from_package(package);
+
+ let mut expected_a = IndexMap::new();
+ expected_a.insert("source".to_string(), "package-pretty-name".to_string());
+ expected_a.insert("target".to_string(), "target-a".to_string());
+ expected_a.insert("reason".to_string(), "reason-a".to_string());
+ let mut expected_b = IndexMap::new();
+ expected_b.insert("source".to_string(), "package-pretty-name".to_string());
+ expected_b.insert("target".to_string(), "target-b".to_string());
+ expected_b.insert("reason".to_string(), "reason-b".to_string());
+ assert_eq!(&vec![expected_a, expected_b], reporter.get_packages());
}
-#[ignore = "asserts IO mock output via getIOMock()->expects(); no IO mocking infrastructure exists and BufferIO::new/get_output are todo!()"]
#[test]
fn test_output() {
- todo!()
+ let (mock, mut reporter, _guard) = set_up();
+ reporter.add_package("a".to_string(), "b".to_string(), "c".to_string());
+
+ mock.borrow_mut()
+ .expects(
+ vec![
+ Expectation::text("a suggests:"),
+ Expectation::text(" - b: c"),
+ Expectation::text(""),
+ ],
+ true,
+ )
+ .unwrap();
+
+ reporter
+ .output(SuggestedPackagesReporter::MODE_BY_PACKAGE, None, None)
+ .unwrap();
}
-#[ignore = "asserts IO mock output via getIOMock()->expects(); no IO mocking infrastructure exists and BufferIO::new/get_output are todo!()"]
#[test]
fn test_output_with_no_suggestion_reason() {
- todo!()
+ let (mock, mut reporter, _guard) = set_up();
+ reporter.add_package("a".to_string(), "b".to_string(), "".to_string());
+
+ mock.borrow_mut()
+ .expects(
+ vec![
+ Expectation::text("a suggests:"),
+ Expectation::text(" - b"),
+ Expectation::text(""),
+ ],
+ true,
+ )
+ .unwrap();
+
+ reporter
+ .output(SuggestedPackagesReporter::MODE_BY_PACKAGE, None, None)
+ .unwrap();
}
-#[ignore = "asserts IO mock output via getIOMock()->expects(); no IO mocking infrastructure exists and BufferIO::new/get_output are todo!()"]
#[test]
fn test_output_ignores_formatting() {
- todo!()
+ let (mock, mut reporter, _guard) = set_up();
+ reporter.add_package(
+ "source".to_string(),
+ "target1".to_string(),
+ "\x1b[1;37;42m Like us\r\non Facebook \x1b[0m".to_string(),
+ );
+ reporter.add_package(
+ "source".to_string(),
+ "target2".to_string(),
+ "<bg=green>Like us on Facebook</>".to_string(),
+ );
+
+ mock.borrow_mut()
+ .expects(
+ vec![
+ Expectation::text("source suggests:"),
+ Expectation::text(" - target1: [1;37;42m Like us on Facebook [0m"),
+ Expectation::text(" - target2: <bg=green>Like us on Facebook</>"),
+ Expectation::text(""),
+ ],
+ true,
+ )
+ .unwrap();
+
+ reporter
+ .output(SuggestedPackagesReporter::MODE_BY_PACKAGE, None, None)
+ .unwrap();
}
-#[ignore = "asserts IO mock output via getIOMock()->expects(); no IO mocking infrastructure exists and BufferIO::new/get_output are todo!()"]
#[test]
fn test_output_multiple_packages() {
- todo!()
+ let (mock, mut reporter, _guard) = set_up();
+ reporter.add_package("a".to_string(), "b".to_string(), "c".to_string());
+ reporter.add_package(
+ "source package".to_string(),
+ "target".to_string(),
+ "because reasons".to_string(),
+ );
+
+ mock.borrow_mut()
+ .expects(
+ vec![
+ Expectation::text("a suggests:"),
+ Expectation::text(" - b: c"),
+ Expectation::text(""),
+ Expectation::text("source package suggests:"),
+ Expectation::text(" - target: because reasons"),
+ Expectation::text(""),
+ ],
+ true,
+ )
+ .unwrap();
+
+ reporter
+ .output(SuggestedPackagesReporter::MODE_BY_PACKAGE, None, None)
+ .unwrap();
}
-#[ignore = "asserts IO mock output via getIOMock()->expects() and uses getMockBuilder mocks of InstalledRepository/PackageInterface; no mocking infrastructure exists"]
#[test]
fn test_output_skip_installed_packages() {
- todo!()
+ let (mock, mut reporter, _guard) = set_up();
+
+ // PHP mocks two PackageInterfaces returning getNames() ['x','y'] and ['b']; only the 'b'
+ // match is consequential (it filters the 'a' -> 'b' suggestion). Real packages carry a
+ // single name, so the immaterial 'y' name is omitted.
+ let package1 = get_package("x", "1.0.0");
+ let package2 = get_package("b", "1.0.0");
+ let installed = LockArrayRepository::new(vec![package1, package2]).unwrap();
+ let mut repository = InstalledRepository::new(vec![RepositoryInterfaceHandle::new(installed)]);
+
+ reporter.add_package("a".to_string(), "b".to_string(), "c".to_string());
+ reporter.add_package(
+ "source package".to_string(),
+ "target".to_string(),
+ "because reasons".to_string(),
+ );
+
+ mock.borrow_mut()
+ .expects(
+ vec![
+ Expectation::text("source package suggests:"),
+ Expectation::text(" - target: because reasons"),
+ Expectation::text(""),
+ ],
+ true,
+ )
+ .unwrap();
+
+ reporter
+ .output(
+ SuggestedPackagesReporter::MODE_BY_PACKAGE,
+ Some(&mut repository),
+ None,
+ )
+ .unwrap();
}
-#[ignore = "uses getMockBuilder mock of InstalledRepository with ->expects($this->exactly(0)) call-count assertion; no mocking infrastructure exists"]
#[test]
fn test_output_not_getting_installed_packages_when_no_suggestions() {
- todo!()
+ let (_mock, reporter, _guard) = set_up();
+
+ // PHP asserts getPackages() is called exactly 0 times. With no suggestions queued,
+ // get_filtered_suggestions short-circuits before touching the repository.
+ let installed = LockArrayRepository::new(vec![]).unwrap();
+ let mut repository = InstalledRepository::new(vec![RepositoryInterfaceHandle::new(installed)]);
+
+ reporter
+ .output(
+ SuggestedPackagesReporter::MODE_BY_PACKAGE,
+ Some(&mut repository),
+ None,
+ )
+ .unwrap();
}
diff --git a/crates/shirabe/tests/package/archiver/archive_manager_test.rs b/crates/shirabe/tests/package/archiver/archive_manager_test.rs
index fdc4bf0..df83f56 100644
--- a/crates/shirabe/tests/package/archiver/archive_manager_test.rs
+++ b/crates/shirabe/tests/package/archiver/archive_manager_test.rs
@@ -1,59 +1,137 @@
//! ref: composer/tests/Composer/Test/Package/Archiver/ArchiveManagerTest.php
-/// Builds an ArchiveManager via Factory/DownloadManager/Loop and derives targetDir under a
-/// unique tmp dir (testDir); none of that factory/fixture infrastructure is ported.
-/// Returns (test_dir, target_dir).
-#[allow(dead_code)]
-fn set_up() -> (String, String) {
- todo!()
-}
+use std::cell::RefCell;
+use std::rc::Rc;
-#[allow(dead_code)]
-fn tear_down(_test_dir: &str) {
- // Removes testDir created in set_up.
- todo!()
-}
+use indexmap::IndexMap;
+use shirabe::config::Config;
+use shirabe::downloader::DownloadManager;
+use shirabe::io::IOInterface;
+use shirabe::io::null_io::NullIO;
+use shirabe::package::CompletePackageInterfaceHandle;
+use shirabe::package::archiver::{ArchiveManager, PharArchiver, ZipArchiver};
+use shirabe::package::handle::CompletePackageHandle;
+use shirabe::util::http_downloader::HttpDownloader;
+use shirabe::util::r#loop::Loop;
+use shirabe_php_shim::realpath;
+use tempfile::TempDir;
-#[allow(dead_code)]
-struct TearDown {
+// ref: ArchiverTestCase::setUp + ArchiveManagerTest::setUp.
+//
+// The PHP test builds the ArchiveManager via Factory/FactoryMock; here we construct it directly
+// with the same archivers (ZipArchiver + PharArchiver) that Factory::createArchiveManager adds.
+struct TestCase {
+ manager: ArchiveManager,
test_dir: String,
+ target_dir: String,
+ _test_dir_guard: TempDir,
}
-impl Drop for TearDown {
- fn drop(&mut self) {
- tear_down(&self.test_dir);
+impl TestCase {
+ fn set_up() -> Self {
+ let guard = TempDir::new().unwrap();
+ let test_dir = guard.path().to_string_lossy().to_string();
+
+ let io: Rc<RefCell<dyn IOInterface>> = Rc::new(RefCell::new(NullIO::new()));
+ let config = Rc::new(RefCell::new(Config::new(false, None)));
+ // The filename/unknown-format tests never drive the download path, so a mock
+ // HttpDownloader (no curl backend) is sufficient to satisfy Loop's dependency.
+ let http_downloader = Rc::new(RefCell::new(HttpDownloader::__new_mock(io.clone(), config)));
+ let dm = Rc::new(RefCell::new(DownloadManager::new(io.clone(), false, None)));
+ let r#loop = Rc::new(RefCell::new(Loop::new(http_downloader, None)));
+
+ let mut manager = ArchiveManager::new(dm, r#loop);
+ manager.add_archiver(Box::new(ZipArchiver::new()));
+ manager.add_archiver(Box::new(PharArchiver::new()));
+
+ let target_dir = format!("{}/composer_archiver_tests", test_dir);
+
+ Self {
+ manager,
+ test_dir,
+ target_dir,
+ _test_dir_guard: guard,
+ }
+ }
+
+ // ref: ArchiverTestCase::setupPackage.
+ fn setup_package(&self) -> CompletePackageInterfaceHandle {
+ let package = CompletePackageHandle::new(
+ "archivertest/archivertest".to_string(),
+ "master".to_string(),
+ "master".to_string(),
+ );
+ package.set_source_url(Some(realpath(&self.test_dir).unwrap_or_default()));
+ package.set_source_reference(Some("master".to_string()));
+ package.__set_source_type(Some("git".to_string()));
+
+ package.into()
}
}
-// These drive ArchiveManager end-to-end (building tar archives via PharData, todo!()) and
-// the filename-derivation helpers over packages; the archiving and fixture setup are not
-// ported.
#[test]
-#[ignore = "needs the Factory/FactoryMock-built ArchiveManager (createDownloadManager/createHttpDownloader/FactoryMock::createConfig) in set_up, which is unported"]
fn test_unknown_format() {
- todo!()
+ let mut test_case = TestCase::set_up();
+ let package = test_case.setup_package();
+
+ let result = test_case.manager.archive(
+ package,
+ "__unknown_format__".to_string(),
+ test_case.target_dir.clone(),
+ None,
+ false,
+ );
+
+ let err = result.expect_err("expected RuntimeException for unknown format");
+ assert!(
+ err.downcast_ref::<shirabe_php_shim::RuntimeException>()
+ .is_some()
+ );
}
+// ref: ArchiveManagerTest::testArchiveTar / testArchiveCustomFileName.
+//
+// These drive ArchiveManager::archive end-to-end for the 'tar' format, which dispatches to
+// PharArchiver::archive. That builds the archive via PharData, whose build_from_iterator is
+// todo!() in the php-shim, so the archiving path cannot run yet.
#[test]
-#[ignore = "needs setupGitRepo + PharData tar archiving and the Factory/FactoryMock-built ArchiveManager (set_up), which is unported"]
+#[ignore = "needs PharData tar archiving (build_from_iterator is todo!() in the php-shim) for ArchiveManager::archive('tar', ...)"]
fn test_archive_tar() {
todo!()
}
#[test]
-#[ignore = "needs setupGitRepo + PharData tar archiving and the Factory/FactoryMock-built ArchiveManager (set_up), which is unported"]
+#[ignore = "needs PharData tar archiving (build_from_iterator is todo!() in the php-shim) for ArchiveManager::archive('tar', ...)"]
fn test_archive_custom_file_name() {
todo!()
}
#[test]
-#[ignore = "needs the Factory/FactoryMock-built ArchiveManager (createDownloadManager/createHttpDownloader/FactoryMock::createConfig) in set_up, which is unported"]
fn test_get_package_filename_parts() {
- todo!()
+ let test_case = TestCase::set_up();
+ let package = test_case.setup_package();
+
+ let mut expected: IndexMap<String, String> = IndexMap::new();
+ expected.insert("base".to_string(), "archivertest-archivertest".to_string());
+ expected.insert("version".to_string(), "master".to_string());
+ expected.insert("source_reference".to_string(), "4f26ae".to_string());
+
+ assert_eq!(
+ expected,
+ test_case
+ .manager
+ .get_package_filename_parts(package)
+ .unwrap()
+ );
}
#[test]
-#[ignore = "needs the Factory/FactoryMock-built ArchiveManager (createDownloadManager/createHttpDownloader/FactoryMock::createConfig) in set_up, which is unported"]
fn test_get_package_filename() {
- todo!()
+ let test_case = TestCase::set_up();
+ let package = test_case.setup_package();
+
+ assert_eq!(
+ "archivertest-archivertest-master-4f26ae",
+ test_case.manager.get_package_filename(package).unwrap()
+ );
}
diff --git a/crates/shirabe/tests/repository/vcs/git_driver_test.rs b/crates/shirabe/tests/repository/vcs/git_driver_test.rs
index 56c2755..7c30b21 100644
--- a/crates/shirabe/tests/repository/vcs/git_driver_test.rs
+++ b/crates/shirabe/tests/repository/vcs/git_driver_test.rs
@@ -1,16 +1,24 @@
//! ref: composer/tests/Composer/Test/Repository/Vcs/GitDriverTest.php
-// Every case constructs a GitDriver with a mocked ProcessExecutor (and an HttpDownloader
-// that reaches curl_multi_init, todo!()) to feed git command output; mocking is not
-// available here.
+use std::cell::RefCell;
+use std::rc::Rc;
use indexmap::IndexMap;
+use serial_test::serial;
use shirabe::config::Config;
+use shirabe::io::IOInterface;
+use shirabe::repository::vcs::GitDriver;
use shirabe::util::filesystem::Filesystem;
+use shirabe::util::http_downloader::HttpDownloaderMockHandler;
use shirabe::util::platform::Platform;
-use shirabe_php_shim::PhpMixed;
+use shirabe::util::process_executor::MockHandler;
+use shirabe_php_shim::{PhpMixed, RuntimeException};
use tempfile::TempDir;
+use crate::http_downloader_mock::{HttpDownloaderMockGuard, get_http_downloader_mock};
+use crate::io_stub::IOStub;
+use crate::process_executor_mock::{ProcessExecutorMockGuard, cmd_full, get_process_executor_mock};
+
struct SetUp {
home: TempDir,
config: Config,
@@ -64,73 +72,255 @@ impl Drop for TearDown {
}
#[test]
-#[ignore = "requires ProcessExecutor mock (getProcessExecutorMock/expects) and Reflection setRepoDir, neither available"]
+#[serial]
fn test_get_root_identifier_from_remote_local_repository() {
let SetUp {
home,
- config: _,
+ config,
network_env,
} = set_up();
+ let home_path = home.path().to_string_lossy().into_owned();
let _tear_down = TearDown::new(home.path().to_path_buf(), network_env);
- todo!()
+
+ let config = Rc::new(RefCell::new(config));
+ let io: Rc<RefCell<dyn IOInterface>> = Rc::new(RefCell::new(IOStub::new()));
+
+ let (http_downloader, _http_guard): (_, HttpDownloaderMockGuard) =
+ get_http_downloader_mock(vec![], false, HttpDownloaderMockHandler::default());
+
+ let stdout = "* main\n 2.2\n 1.10";
+
+ let (process, _process_guard): (_, ProcessExecutorMockGuard) = get_process_executor_mock(
+ vec![cmd_full(["git", "branch", "--no-color"], 0, stdout, "")],
+ true,
+ MockHandler::default(),
+ );
+
+ let mut repo_config: IndexMap<String, PhpMixed> = IndexMap::new();
+ repo_config.insert("url".to_string(), PhpMixed::String(home_path.clone()));
+
+ let mut driver = GitDriver::new(repo_config, io, config, http_downloader, process);
+ driver.__set_repo_dir(home_path);
+
+ assert_eq!("main", driver.get_root_identifier().unwrap());
}
#[test]
-#[ignore = "requires ProcessExecutor mock (getProcessExecutorMock/expects), IO mock and Reflection setRepoDir, none available"]
+#[serial]
fn test_get_root_identifier_from_remote() {
let SetUp {
home,
- config: _,
+ config,
network_env,
} = set_up();
+ let home_path = home.path().to_string_lossy().into_owned();
let _tear_down = TearDown::new(home.path().to_path_buf(), network_env);
- todo!()
+
+ let config = Rc::new(RefCell::new(config));
+ let io: Rc<RefCell<dyn IOInterface>> = Rc::new(RefCell::new(IOStub::new()));
+
+ let (http_downloader, _http_guard): (_, HttpDownloaderMockGuard) =
+ get_http_downloader_mock(vec![], false, HttpDownloaderMockHandler::default());
+
+ let stdout = "* remote origin\n Fetch URL: https://example.org/acme.git\n Push URL: https://example.org/acme.git\n HEAD branch: main\n Remote branches:\n 1.10 tracked\n 2.2 tracked\n main tracked";
+
+ let (process, _process_guard): (_, ProcessExecutorMockGuard) = get_process_executor_mock(
+ vec![
+ cmd_full(["git", "remote", "-v"], 0, "", ""),
+ cmd_full(
+ [
+ "git",
+ "remote",
+ "set-url",
+ "origin",
+ "--",
+ "https://example.org/acme.git",
+ ],
+ 0,
+ "",
+ "",
+ ),
+ cmd_full(["git", "remote", "show", "origin"], 0, stdout, ""),
+ cmd_full(
+ [
+ "git",
+ "remote",
+ "set-url",
+ "origin",
+ "--",
+ "https://example.org/acme.git",
+ ],
+ 0,
+ "",
+ "",
+ ),
+ ],
+ false,
+ MockHandler::default(),
+ );
+
+ let mut repo_config: IndexMap<String, PhpMixed> = IndexMap::new();
+ repo_config.insert(
+ "url".to_string(),
+ PhpMixed::String("https://example.org/acme.git".to_string()),
+ );
+
+ let mut driver = GitDriver::new(repo_config, io, config, http_downloader, process);
+ driver.__set_repo_dir(home_path);
+
+ assert_eq!("main", driver.get_root_identifier().unwrap());
}
#[test]
-#[ignore = "requires ProcessExecutor mock (getProcessExecutorMock/expects) and Reflection setRepoDir, neither available"]
+#[serial]
fn test_get_root_identifier_from_local_with_network_disabled() {
let SetUp {
home,
- config: _,
+ config,
network_env,
} = set_up();
+ let home_path = home.path().to_string_lossy().into_owned();
let _tear_down = TearDown::new(home.path().to_path_buf(), network_env);
- todo!()
+
+ Platform::put_env("COMPOSER_DISABLE_NETWORK", "1");
+
+ let config = Rc::new(RefCell::new(config));
+ let io: Rc<RefCell<dyn IOInterface>> = Rc::new(RefCell::new(IOStub::new()));
+
+ let (http_downloader, _http_guard): (_, HttpDownloaderMockGuard) =
+ get_http_downloader_mock(vec![], false, HttpDownloaderMockHandler::default());
+
+ let stdout = "* main\n 2.2\n 1.10";
+
+ let (process, _process_guard): (_, ProcessExecutorMockGuard) = get_process_executor_mock(
+ vec![cmd_full(["git", "branch", "--no-color"], 0, stdout, "")],
+ false,
+ MockHandler::default(),
+ );
+
+ let mut repo_config: IndexMap<String, PhpMixed> = IndexMap::new();
+ repo_config.insert(
+ "url".to_string(),
+ PhpMixed::String("https://example.org/acme.git".to_string()),
+ );
+
+ let mut driver = GitDriver::new(repo_config, io, config, http_downloader, process);
+ driver.__set_repo_dir(home_path);
+
+ assert_eq!("main", driver.get_root_identifier().unwrap());
}
#[test]
-#[ignore = "requires ProcessExecutor mock (getProcessExecutorMock/expects), IOInterface mock and Reflection setRepoDir, none available"]
fn test_get_branches_filter_invalid_branch_names() {
let SetUp {
home,
- config: _,
+ config,
network_env,
} = set_up();
+ let home_path = home.path().to_string_lossy().into_owned();
let _tear_down = TearDown::new(home.path().to_path_buf(), network_env);
- todo!()
+
+ let config = Rc::new(RefCell::new(config));
+ let io: Rc<RefCell<dyn IOInterface>> = Rc::new(RefCell::new(IOStub::new()));
+
+ let (http_downloader, _http_guard): (_, HttpDownloaderMockGuard) =
+ get_http_downloader_mock(vec![], false, HttpDownloaderMockHandler::default());
+
+ // Branches starting with a - character are not valid git branch names.
+ // Still assert that they get filtered to prevent issues later on.
+ let stdout = "* main 089681446ba44d6d9004350192486f2ceb4eaa06 commit\n 2.2 12681446ba44d6d9004350192486f2ceb4eaa06 commit\n -h 089681446ba44d6d9004350192486f2ceb4eaa06 commit";
+
+ let (process, _process_guard): (_, ProcessExecutorMockGuard) = get_process_executor_mock(
+ vec![cmd_full(
+ ["git", "branch", "--no-color", "--no-abbrev", "-v"],
+ 0,
+ stdout,
+ "",
+ )],
+ false,
+ MockHandler::default(),
+ );
+
+ let mut repo_config: IndexMap<String, PhpMixed> = IndexMap::new();
+ repo_config.insert(
+ "url".to_string(),
+ PhpMixed::String("https://example.org/acme.git".to_string()),
+ );
+
+ let mut driver = GitDriver::new(repo_config, io, config, http_downloader, process);
+ driver.__set_repo_dir(home_path);
+
+ let branches = driver.get_branches().unwrap();
+ let mut expected: IndexMap<String, String> = IndexMap::new();
+ expected.insert(
+ "main".to_string(),
+ "089681446ba44d6d9004350192486f2ceb4eaa06".to_string(),
+ );
+ expected.insert(
+ "2.2".to_string(),
+ "12681446ba44d6d9004350192486f2ceb4eaa06".to_string(),
+ );
+ assert_eq!(expected, branches);
}
#[test]
-#[ignore = "requires ProcessExecutor mock (getProcessExecutorMock) and IOInterface/HttpDownloader mocks, none available"]
fn test_file_get_content_invalid_identifier() {
let SetUp {
home,
- config: _,
+ config,
network_env,
} = set_up();
let _tear_down = TearDown::new(home.path().to_path_buf(), network_env);
- todo!()
+
+ let config = Rc::new(RefCell::new(config));
+ let io: Rc<RefCell<dyn IOInterface>> = Rc::new(RefCell::new(IOStub::new()));
+
+ let (http_downloader, _http_guard): (_, HttpDownloaderMockGuard) =
+ get_http_downloader_mock(vec![], false, HttpDownloaderMockHandler::default());
+
+ let (process, _process_guard): (_, ProcessExecutorMockGuard) =
+ get_process_executor_mock(vec![], false, MockHandler::default());
+
+ let mut repo_config: IndexMap<String, PhpMixed> = IndexMap::new();
+ repo_config.insert(
+ "url".to_string(),
+ PhpMixed::String("https://example.org/acme.git".to_string()),
+ );
+
+ let mut driver = GitDriver::new(repo_config, io, config, http_downloader, process);
+
+ assert_eq!(None, driver.get_file_content("file.txt", "h").unwrap());
+
+ let err = driver.get_file_content("file.txt", "-h").unwrap_err();
+ assert!(err.downcast_ref::<RuntimeException>().is_some());
}
#[test]
-#[ignore = "requires ProcessExecutor mock (getProcessExecutorMock) and IOInterface/HttpDownloader mocks, none available"]
fn test_get_change_date_invalid_identifier() {
let SetUp {
home,
- config: _,
+ config,
network_env,
} = set_up();
let _tear_down = TearDown::new(home.path().to_path_buf(), network_env);
- todo!()
+
+ let config = Rc::new(RefCell::new(config));
+ let io: Rc<RefCell<dyn IOInterface>> = Rc::new(RefCell::new(IOStub::new()));
+
+ let (http_downloader, _http_guard): (_, HttpDownloaderMockGuard) =
+ get_http_downloader_mock(vec![], false, HttpDownloaderMockHandler::default());
+
+ let (process, _process_guard): (_, ProcessExecutorMockGuard) =
+ get_process_executor_mock(vec![], false, MockHandler::default());
+
+ let mut repo_config: IndexMap<String, PhpMixed> = IndexMap::new();
+ repo_config.insert(
+ "url".to_string(),
+ PhpMixed::String("https://example.org/acme.git".to_string()),
+ );
+
+ let mut driver = GitDriver::new(repo_config, io, config, http_downloader, process);
+
+ let err = driver.get_change_date("-n1 --format=%at HEAD").unwrap_err();
+ assert!(err.downcast_ref::<RuntimeException>().is_some());
}
diff --git a/crates/shirabe/tests/util/bitbucket_test.rs b/crates/shirabe/tests/util/bitbucket_test.rs
index 3e3fad8..d57a49a 100644
--- a/crates/shirabe/tests/util/bitbucket_test.rs
+++ b/crates/shirabe/tests/util/bitbucket_test.rs
@@ -1,100 +1,660 @@
//! ref: composer/tests/Composer/Test/Util/BitbucketTest.php
-// These mock IO/Config/HttpDownloader to drive Bitbucket's OAuth/access-token flow; mocking
-// is not available and a real HttpDownloader reaches curl_multi_init (todo!()).
+use std::cell::RefCell;
+use std::rc::Rc;
-#[allow(dead_code)]
-fn set_up() {
- // Builds mocked IO/HttpDownloader/Config and records time(); mocking is not available.
- todo!()
+use indexmap::IndexMap;
+use shirabe::config::{Config, ConfigSourceInterface};
+use shirabe::io::IOInterface;
+use shirabe::io::io_interface;
+use shirabe::util::Bitbucket;
+use shirabe::util::http_downloader::{
+ HttpDownloader, HttpDownloaderMockExpectation, HttpDownloaderMockHandler,
+};
+use shirabe::util::process_executor::MockHandler;
+use shirabe_php_shim::{PhpMixed, time};
+
+use crate::config_stub::ConfigStubBuilder;
+use crate::http_downloader_mock::{expect_full, get_http_downloader_mock};
+use crate::io_mock::{Expectation, IOMock, get_io_mock};
+use crate::process_executor_mock::get_process_executor_mock;
+
+const USERNAME: &str = "username";
+const PASSWORD: &str = "password";
+const CONSUMER_KEY: &str = "consumer_key";
+const CONSUMER_SECRET: &str = "consumer_secret";
+const MESSAGE: &str = "mymessage";
+const ORIGIN: &str = "bitbucket.org";
+const TOKEN: &str = "bitbuckettoken";
+
+// Records add/removeConfigSetting calls and serves a configurable getName, mirroring
+// the PHPUnit mock of ConfigSourceInterface used throughout BitbucketTest.
+#[derive(Debug, Clone, Default)]
+struct ConfigSourceCalls {
+ added: Vec<(String, PhpMixed)>,
+ removed: Vec<String>,
+}
+
+#[derive(Debug)]
+struct ConfigSourceMock {
+ name: String,
+ calls: Rc<RefCell<ConfigSourceCalls>>,
+}
+
+impl ConfigSourceInterface for ConfigSourceMock {
+ fn add_repository(
+ &mut self,
+ _name: &str,
+ _config: PhpMixed,
+ _append: bool,
+ ) -> anyhow::Result<()> {
+ unreachable!()
+ }
+ fn insert_repository(
+ &mut self,
+ _name: &str,
+ _config: PhpMixed,
+ _reference_name: &str,
+ _offset: i64,
+ ) -> anyhow::Result<()> {
+ unreachable!()
+ }
+ fn set_repository_url(&mut self, _name: &str, _url: &str) -> anyhow::Result<()> {
+ unreachable!()
+ }
+ fn remove_repository(&mut self, _name: &str) -> anyhow::Result<()> {
+ unreachable!()
+ }
+ fn add_config_setting(&mut self, name: &str, value: PhpMixed) -> anyhow::Result<()> {
+ self.calls
+ .borrow_mut()
+ .added
+ .push((name.to_string(), value));
+ Ok(())
+ }
+ fn remove_config_setting(&mut self, name: &str) -> anyhow::Result<()> {
+ self.calls.borrow_mut().removed.push(name.to_string());
+ Ok(())
+ }
+ fn add_property(&mut self, _name: &str, _value: PhpMixed) -> anyhow::Result<()> {
+ unreachable!()
+ }
+ fn remove_property(&mut self, _name: &str) -> anyhow::Result<()> {
+ unreachable!()
+ }
+ fn add_link(&mut self, _type: &str, _name: &str, _value: &str) -> anyhow::Result<()> {
+ unreachable!()
+ }
+ fn remove_link(&mut self, _type: &str, _name: &str) -> anyhow::Result<()> {
+ unreachable!()
+ }
+ fn get_name(&self) -> String {
+ self.name.clone()
+ }
+}
+
+// Mirrors BitbucketTest::setUp: a DEBUG-verbosity IOMock, a mocked HttpDownloader, a
+// real Config, the captured `time()`, and the Bitbucket under test. The mock guards
+// run their assert_complete on drop at the end of the test scope.
+struct Fixture {
+ io: Rc<RefCell<IOMock>>,
+ config: Rc<RefCell<Config>>,
+ http_downloader: Rc<RefCell<HttpDownloader>>,
+ time: i64,
+ bitbucket: Bitbucket,
+ _io_guard: crate::io_mock::IOMockGuard,
+ _http_guard: crate::http_downloader_mock::HttpDownloaderMockGuard,
+}
+
+fn set_up_with_config_and_http(
+ config: Rc<RefCell<Config>>,
+ http_expectations: Vec<HttpDownloaderMockExpectation>,
+) -> Fixture {
+ let (io_mock, io_guard) = get_io_mock(io_interface::DEBUG).unwrap();
+ let (http_downloader, http_guard) = get_http_downloader_mock(
+ http_expectations,
+ true,
+ HttpDownloaderMockHandler::default(),
+ );
+
+ let io: Rc<RefCell<dyn IOInterface>> = io_mock.clone();
+ let time = time();
+ let bitbucket = Bitbucket::new(
+ io,
+ config.clone(),
+ None,
+ Some(http_downloader.clone()),
+ Some(time),
+ )
+ .unwrap();
+
+ Fixture {
+ io: io_mock,
+ config,
+ http_downloader,
+ time,
+ bitbucket,
+ _io_guard: io_guard,
+ _http_guard: http_guard,
+ }
+}
+
+// The OAuth2 token request as built by Bitbucket::request_access_token.
+fn access_token_request_options() -> IndexMap<String, PhpMixed> {
+ let mut http = IndexMap::new();
+ http.insert("method".to_string(), PhpMixed::String("POST".to_string()));
+ http.insert(
+ "content".to_string(),
+ PhpMixed::String("grant_type=client_credentials".to_string()),
+ );
+ let mut options: IndexMap<String, PhpMixed> = IndexMap::new();
+ options.insert("retry-auth-failure".to_string(), PhpMixed::Bool(false));
+ options.insert("http".to_string(), PhpMixed::Array(http));
+ options
+}
+
+fn access_token_body() -> String {
+ format!(
+ "{{\"access_token\": \"{}\", \"scopes\": \"repository\", \"expires_in\": 3600, \"refresh_token\": \"refreshtoken\", \"token_type\": \"bearer\"}}",
+ TOKEN
+ )
+}
+
+// Installs recording config/auth config sources and returns their shared call logs,
+// mirroring BitbucketTest::setExpectationsForStoringAccessToken.
+struct StoreAccessTokenMocks {
+ config_source: Rc<RefCell<ConfigSourceCalls>>,
+ auth_config_source: Rc<RefCell<ConfigSourceCalls>>,
+}
+
+fn set_expectations_for_storing_access_token(
+ config: &Rc<RefCell<Config>>,
+) -> StoreAccessTokenMocks {
+ let config_source = Rc::new(RefCell::new(ConfigSourceCalls::default()));
+ let auth_config_source = Rc::new(RefCell::new(ConfigSourceCalls::default()));
+ config
+ .borrow_mut()
+ .set_config_source(Box::new(ConfigSourceMock {
+ name: "config-source".to_string(),
+ calls: config_source.clone(),
+ }));
+ config
+ .borrow_mut()
+ .set_auth_config_source(Box::new(ConfigSourceMock {
+ name: "auth-config-source".to_string(),
+ calls: auth_config_source.clone(),
+ }));
+ StoreAccessTokenMocks {
+ config_source,
+ auth_config_source,
+ }
+}
+
+fn expected_stored_token(time: i64) -> PhpMixed {
+ let mut consumer = IndexMap::new();
+ consumer.insert(
+ "consumer-key".to_string(),
+ PhpMixed::String(CONSUMER_KEY.to_string()),
+ );
+ consumer.insert(
+ "consumer-secret".to_string(),
+ PhpMixed::String(CONSUMER_SECRET.to_string()),
+ );
+ consumer.insert(
+ "access-token".to_string(),
+ PhpMixed::String(TOKEN.to_string()),
+ );
+ consumer.insert(
+ "access-token-expiration".to_string(),
+ PhpMixed::Int(time + 3600),
+ );
+ PhpMixed::Array(consumer)
+}
+
+fn assert_stored_access_token(mocks: &StoreAccessTokenMocks, time: i64, remove_basic_auth: bool) {
+ assert_eq!(
+ mocks.config_source.borrow().removed,
+ vec![format!("bitbucket-oauth.{}", ORIGIN)],
+ );
+ assert_eq!(
+ mocks.auth_config_source.borrow().added,
+ vec![(
+ format!("bitbucket-oauth.{}", ORIGIN),
+ expected_stored_token(time)
+ )],
+ );
+ if remove_basic_auth {
+ assert_eq!(
+ mocks.auth_config_source.borrow().removed,
+ vec![format!("http-basic.{}", ORIGIN)],
+ );
+ }
}
#[test]
-#[ignore = "needs getIOMock/IOMock and getMockBuilder mocks for HttpDownloader/Config (no mock infrastructure)"]
fn test_request_access_token_with_valid_oauth_consumer() {
- todo!()
+ let config = ConfigStubBuilder::new().build_shared();
+ let mut f = set_up_with_config_and_http(
+ config,
+ vec![expect_full(
+ Bitbucket::OAUTH2_ACCESS_TOKEN_URL,
+ Some(access_token_request_options()),
+ 200,
+ access_token_body(),
+ vec![],
+ )],
+ );
+
+ f.io.borrow_mut()
+ .expects(
+ vec![Expectation::auth(
+ ORIGIN,
+ CONSUMER_KEY,
+ Some(CONSUMER_SECRET.to_string()),
+ )],
+ false,
+ )
+ .unwrap();
+
+ let mocks = set_expectations_for_storing_access_token(&f.config);
+
+ assert_eq!(
+ f.bitbucket
+ .request_token(ORIGIN, CONSUMER_KEY, CONSUMER_SECRET)
+ .unwrap(),
+ TOKEN
+ );
+
+ assert_stored_access_token(&mocks, f.time, false);
}
#[test]
-#[ignore = "needs getMockBuilder mock for Config (no mock infrastructure)"]
fn test_request_access_token_with_valid_oauth_consumer_and_valid_stored_access_token() {
- todo!()
+ let time = time();
+ let mut stored = IndexMap::new();
+ stored.insert(
+ "access-token".to_string(),
+ PhpMixed::String(TOKEN.to_string()),
+ );
+ stored.insert(
+ "access-token-expiration".to_string(),
+ PhpMixed::Int(time + 1800),
+ );
+ stored.insert(
+ "consumer-key".to_string(),
+ PhpMixed::String(CONSUMER_KEY.to_string()),
+ );
+ stored.insert(
+ "consumer-secret".to_string(),
+ PhpMixed::String(CONSUMER_SECRET.to_string()),
+ );
+ let mut oauth = IndexMap::new();
+ oauth.insert(ORIGIN.to_string(), PhpMixed::Array(stored));
+
+ let config = ConfigStubBuilder::new()
+ .with("bitbucket-oauth", PhpMixed::Array(oauth))
+ .build_shared();
+ let mut f = set_up_with_config_and_http(config, vec![]);
+ f.time = time;
+
+ assert_eq!(
+ f.bitbucket
+ .request_token(ORIGIN, CONSUMER_KEY, CONSUMER_SECRET)
+ .unwrap(),
+ TOKEN
+ );
+
+ // testGetTokenWithAccessToken (@depends): the same Bitbucket now returns the token.
+ assert_eq!(f.bitbucket.get_token(), TOKEN);
}
#[test]
-#[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!()
+ let time = time();
+ let mut stored = IndexMap::new();
+ stored.insert(
+ "access-token".to_string(),
+ PhpMixed::String("randomExpiredToken".to_string()),
+ );
+ stored.insert(
+ "access-token-expiration".to_string(),
+ PhpMixed::Int(time - 400),
+ );
+ stored.insert(
+ "consumer-key".to_string(),
+ PhpMixed::String(CONSUMER_KEY.to_string()),
+ );
+ stored.insert(
+ "consumer-secret".to_string(),
+ PhpMixed::String(CONSUMER_SECRET.to_string()),
+ );
+ let mut oauth = IndexMap::new();
+ oauth.insert(ORIGIN.to_string(), PhpMixed::Array(stored));
+
+ let config = ConfigStubBuilder::new()
+ .with("bitbucket-oauth", PhpMixed::Array(oauth))
+ .build_shared();
+ let mut f = set_up_with_config_and_http(
+ config,
+ vec![expect_full(
+ Bitbucket::OAUTH2_ACCESS_TOKEN_URL,
+ Some(access_token_request_options()),
+ 200,
+ access_token_body(),
+ vec![],
+ )],
+ );
+ f.time = time;
+
+ f.io.borrow_mut()
+ .expects(
+ vec![Expectation::auth(
+ ORIGIN,
+ CONSUMER_KEY,
+ Some(CONSUMER_SECRET.to_string()),
+ )],
+ false,
+ )
+ .unwrap();
+
+ let mocks = set_expectations_for_storing_access_token(&f.config);
+
+ assert_eq!(
+ f.bitbucket
+ .request_token(ORIGIN, CONSUMER_KEY, CONSUMER_SECRET)
+ .unwrap(),
+ TOKEN
+ );
+
+ assert_stored_access_token(&mocks, f.time, false);
}
#[test]
-#[ignore = "needs getIOMock/IOMock and getMockBuilder mocks for HttpDownloader/Config (no mock infrastructure)"]
fn test_request_access_token_with_username_and_password() {
- todo!()
+ let config = ConfigStubBuilder::new().build_shared();
+ // A 400 status makes the mocked HttpDownloader raise a TransportException(400),
+ // matching PHP's willThrowException for the BAD REQUEST case.
+ let mut f = set_up_with_config_and_http(
+ config,
+ vec![expect_full(
+ Bitbucket::OAUTH2_ACCESS_TOKEN_URL,
+ Some(access_token_request_options()),
+ 400,
+ "",
+ vec![],
+ )],
+ );
+
+ f.io
+ .borrow_mut()
+ .expects(
+ vec![
+ Expectation::auth(ORIGIN, USERNAME, Some(PASSWORD.to_string())),
+ Expectation::text("Invalid OAuth consumer provided."),
+ Expectation::text("This can have three reasons:"),
+ Expectation::text(
+ "1. You are authenticating with a bitbucket username/password combination",
+ ),
+ Expectation::text(
+ "2. You are using an OAuth consumer, but didn't configure a (dummy) callback url",
+ ),
+ Expectation::text(
+ "3. You are using an OAuth consumer, but didn't configure it as private consumer",
+ ),
+ ],
+ true,
+ )
+ .unwrap();
+
+ assert_eq!(
+ f.bitbucket
+ .request_token(ORIGIN, USERNAME, PASSWORD)
+ .unwrap(),
+ ""
+ );
}
#[test]
-#[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!()
+ let config = ConfigStubBuilder::new().build_shared();
+ let mut f = set_up_with_config_and_http(
+ config,
+ vec![expect_full(
+ Bitbucket::OAUTH2_ACCESS_TOKEN_URL,
+ Some(access_token_request_options()),
+ 401,
+ "",
+ vec![],
+ )],
+ );
+
+ f.io
+ .borrow_mut()
+ .expects(
+ vec![
+ Expectation::auth(ORIGIN, USERNAME, Some(PASSWORD.to_string())),
+ Expectation::text("Invalid OAuth consumer provided."),
+ Expectation::text(
+ "You can also add it manually later by using \"composer config --global --auth bitbucket-oauth.bitbucket.org <consumer-key> <consumer-secret>\"",
+ ),
+ ],
+ true,
+ )
+ .unwrap();
+
+ assert_eq!(
+ f.bitbucket
+ .request_token(ORIGIN, USERNAME, PASSWORD)
+ .unwrap(),
+ ""
+ );
}
#[test]
-#[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!()
+ let config = ConfigStubBuilder::new().build_shared();
+ let mut f = set_up_with_config_and_http(
+ config,
+ vec![expect_full(
+ Bitbucket::OAUTH2_ACCESS_TOKEN_URL,
+ Some(access_token_request_options()),
+ 404,
+ "",
+ vec![],
+ )],
+ );
+
+ f.io.borrow_mut()
+ .expects(
+ vec![Expectation::auth(
+ ORIGIN,
+ USERNAME,
+ Some(PASSWORD.to_string()),
+ )],
+ false,
+ )
+ .unwrap();
+
+ let result = f.bitbucket.request_token(ORIGIN, USERNAME, PASSWORD);
+ assert!(
+ result.is_err(),
+ "expected a TransportException to propagate"
+ );
}
#[test]
-#[ignore = "needs getIOMock/IOMock and getMockBuilder mocks for HttpDownloader/Config (no mock infrastructure)"]
fn test_username_password_authentication_flow() {
- todo!()
+ let url = format!("https://{}/site/oauth2/access_token", ORIGIN);
+ let body = format!(
+ "{{\"access_token\": \"{}\", \"scopes\": \"repository\", \"expires_in\": 3600, \"refresh_token\": \"refresh_token\", \"token_type\": \"bearer\"}}",
+ TOKEN
+ );
+ // PHP matches the URL with `$this->anything()` for options, so match any options.
+ let config = ConfigStubBuilder::new().build_shared();
+ let mut f =
+ set_up_with_config_and_http(config, vec![expect_full(url, None, 200, body, vec![])]);
+
+ f.io.borrow_mut()
+ .expects(
+ vec![
+ Expectation::text(MESSAGE),
+ Expectation::ask("Consumer Key (hidden): ", CONSUMER_KEY),
+ Expectation::ask("Consumer Secret (hidden): ", CONSUMER_SECRET),
+ ],
+ false,
+ )
+ .unwrap();
+
+ let mocks = set_expectations_for_storing_access_token(&f.config);
+
+ assert!(
+ f.bitbucket
+ .authorize_oauth_interactively(ORIGIN, Some(MESSAGE))
+ .unwrap()
+ );
+
+ assert_stored_access_token(&mocks, f.time, true);
}
#[test]
-#[ignore = "needs getIOMock/IOMock and getMockBuilder mock for Config (no mock infrastructure)"]
fn test_authorize_oauth_interactively_with_empty_username() {
- todo!()
+ let config = ConfigStubBuilder::new().build_shared();
+ let mut f = set_up_with_config_and_http(config, vec![]);
+
+ // getAuthConfigSource() is consulted while printing the instructions.
+ let auth_calls = Rc::new(RefCell::new(ConfigSourceCalls::default()));
+ f.config
+ .borrow_mut()
+ .set_auth_config_source(Box::new(ConfigSourceMock {
+ name: "auth-config-source".to_string(),
+ calls: auth_calls,
+ }));
+
+ f.io.borrow_mut()
+ .expects(vec![Expectation::ask("Consumer Key (hidden): ", "")], false)
+ .unwrap();
+
+ assert!(
+ !f.bitbucket
+ .authorize_oauth_interactively(ORIGIN, Some(MESSAGE))
+ .unwrap()
+ );
}
#[test]
-#[ignore = "needs getIOMock/IOMock and getMockBuilder mock for Config (no mock infrastructure)"]
fn test_authorize_oauth_interactively_with_empty_password() {
- todo!()
+ let config = ConfigStubBuilder::new().build_shared();
+ let mut f = set_up_with_config_and_http(config, vec![]);
+
+ let auth_calls = Rc::new(RefCell::new(ConfigSourceCalls::default()));
+ f.config
+ .borrow_mut()
+ .set_auth_config_source(Box::new(ConfigSourceMock {
+ name: "auth-config-source".to_string(),
+ calls: auth_calls,
+ }));
+
+ f.io.borrow_mut()
+ .expects(
+ vec![
+ Expectation::text(MESSAGE),
+ Expectation::ask("Consumer Key (hidden): ", CONSUMER_KEY),
+ Expectation::ask("Consumer Secret (hidden): ", ""),
+ ],
+ false,
+ )
+ .unwrap();
+
+ assert!(
+ !f.bitbucket
+ .authorize_oauth_interactively(ORIGIN, Some(MESSAGE))
+ .unwrap()
+ );
}
#[test]
-#[ignore = "needs getIOMock/IOMock and getMockBuilder mocks for HttpDownloader/Config (no mock infrastructure)"]
fn test_authorize_oauth_interactively_with_request_access_token_failure() {
- todo!()
-}
+ let url = format!("https://{}/site/oauth2/access_token", ORIGIN);
+ let config = ConfigStubBuilder::new().build_shared();
+ // A 400 status makes the mocked HttpDownloader raise a TransportException(400).
+ let mut f = set_up_with_config_and_http(config, vec![expect_full(url, None, 400, "", vec![])]);
-#[test]
-#[ignore = "needs getIOMock/IOMock and getMockBuilder mocks for HttpDownloader/Config to construct Bitbucket (no mock infrastructure)"]
-fn test_get_token_without_access_token() {
- todo!()
+ let auth_calls = Rc::new(RefCell::new(ConfigSourceCalls::default()));
+ f.config
+ .borrow_mut()
+ .set_auth_config_source(Box::new(ConfigSourceMock {
+ name: "auth-config-source".to_string(),
+ calls: auth_calls,
+ }));
+
+ f.io.borrow_mut()
+ .expects(
+ vec![
+ Expectation::text(MESSAGE),
+ Expectation::ask("Consumer Key (hidden): ", CONSUMER_KEY),
+ Expectation::ask("Consumer Secret (hidden): ", CONSUMER_SECRET),
+ ],
+ false,
+ )
+ .unwrap();
+
+ assert!(
+ !f.bitbucket
+ .authorize_oauth_interactively(ORIGIN, Some(MESSAGE))
+ .unwrap()
+ );
}
#[test]
-#[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!()
+fn test_get_token_without_access_token() {
+ let config = ConfigStubBuilder::new().build_shared();
+ let f = set_up_with_config_and_http(config, vec![]);
+ assert_eq!(f.bitbucket.get_token(), "");
}
#[test]
-#[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!()
+ let config = ConfigStubBuilder::new().build_shared();
+ let mut f = set_up_with_config_and_http(config, vec![]);
+ assert!(!f.bitbucket.authorize_oauth(&format!("non-{}", ORIGIN)));
}
#[test]
-#[ignore = "needs getIOMock/IOMock, getProcessExecutorMock and getMockBuilder mocks for HttpDownloader/Config (no mock infrastructure)"]
fn test_authorize_oauth_without_available_git_config_token() {
- todo!()
+ let config = ConfigStubBuilder::new().build_shared();
+ let (io_mock, _io_guard) = get_io_mock(io_interface::DEBUG).unwrap();
+ let (http_downloader, _http_guard) =
+ get_http_downloader_mock(vec![], true, HttpDownloaderMockHandler::default());
+ let (process, _process_guard) = get_process_executor_mock(
+ vec![],
+ false,
+ MockHandler {
+ r#return: -1,
+ ..Default::default()
+ },
+ );
+
+ let io: Rc<RefCell<dyn IOInterface>> = io_mock.clone();
+ let time = time();
+ let mut bitbucket =
+ Bitbucket::new(io, config, Some(process), Some(http_downloader), Some(time)).unwrap();
+
+ assert!(!bitbucket.authorize_oauth(ORIGIN));
}
#[test]
-#[ignore = "needs getIOMock/IOMock, getProcessExecutorMock and getMockBuilder mocks for HttpDownloader/Config (no mock infrastructure)"]
fn test_authorize_oauth_with_available_git_config_token() {
- todo!()
+ let config = ConfigStubBuilder::new().build_shared();
+ let (io_mock, _io_guard) = get_io_mock(io_interface::DEBUG).unwrap();
+ let (http_downloader, _http_guard) =
+ get_http_downloader_mock(vec![], true, HttpDownloaderMockHandler::default());
+ let (process, _process_guard) =
+ get_process_executor_mock(vec![], false, MockHandler::default());
+
+ let io: Rc<RefCell<dyn IOInterface>> = io_mock.clone();
+ let time = time();
+ let mut bitbucket =
+ Bitbucket::new(io, config, Some(process), Some(http_downloader), Some(time)).unwrap();
+
+ assert!(bitbucket.authorize_oauth(ORIGIN));
}