aboutsummaryrefslogtreecommitdiffhomepage
path: root/crates/shirabe/tests
diff options
context:
space:
mode:
Diffstat (limited to 'crates/shirabe/tests')
-rw-r--r--crates/shirabe/tests/command/about_command_test.rs30
-rw-r--r--crates/shirabe/tests/command/bump_command_test.rs5
-rw-r--r--crates/shirabe/tests/command/check_platform_reqs_command_test.rs12
-rw-r--r--crates/shirabe/tests/command/clear_cache_command_test.rs66
-rw-r--r--crates/shirabe/tests/command/config_command_test.rs497
-rw-r--r--crates/shirabe/tests/command/diagnose_command_test.rs6
-rw-r--r--crates/shirabe/tests/command/home_command_test.rs5
-rw-r--r--crates/shirabe/tests/command/validate_command_test.rs156
-rw-r--r--crates/shirabe/tests/package/loader/root_package_loader_test.rs2
-rw-r--r--crates/shirabe/tests/package/loader/validating_array_loader_test.rs6
-rw-r--r--crates/shirabe/tests/package/root_alias_package_test.rs4
-rw-r--r--crates/shirabe/tests/repository/artifact_repository_test.rs6
-rw-r--r--crates/shirabe/tests/repository/path_repository_test.rs10
-rw-r--r--crates/shirabe/tests/repository/repository_factory_test.rs2
-rw-r--r--crates/shirabe/tests/repository/repository_manager_test.rs5
-rw-r--r--crates/shirabe/tests/util/forgejo_test.rs183
-rw-r--r--crates/shirabe/tests/util/gitlab_test.rs171
-rw-r--r--crates/shirabe/tests/util/tls_helper_test.rs133
18 files changed, 1227 insertions, 72 deletions
diff --git a/crates/shirabe/tests/command/about_command_test.rs b/crates/shirabe/tests/command/about_command_test.rs
index 591fbfa..bb726cf 100644
--- a/crates/shirabe/tests/command/about_command_test.rs
+++ b/crates/shirabe/tests/command/about_command_test.rs
@@ -1,7 +1,33 @@
//! ref: composer/tests/Composer/Test/Command/AboutCommandTest.php
+use crate::test_case::{RunOptions, get_application_tester};
+use serial_test::serial;
+use shirabe::composer;
+use shirabe_php_shim::PhpMixed;
+
#[test]
-#[ignore = "missing get_application_tester (ApplicationTester) infrastructure and Application::get_display/set_auto_exit"]
+#[serial]
fn test_about() {
- todo!()
+ let composer_version = composer::get_version();
+ let mut app_tester = get_application_tester();
+ let status_code = app_tester
+ .run(
+ vec![(PhpMixed::from("command"), PhpMixed::from("about"))],
+ RunOptions::default(),
+ )
+ .unwrap();
+ assert_eq!(0, status_code);
+
+ assert!(app_tester.get_display().contains(&format!(
+ "Composer - Dependency Manager for PHP - version {composer_version}"
+ )));
+
+ assert!(app_tester.get_display().contains(
+ "Composer is a dependency manager tracking local dependencies of your projects and libraries."
+ ));
+ assert!(
+ app_tester
+ .get_display()
+ .contains("See https://getcomposer.org/ for more information.")
+ );
}
diff --git a/crates/shirabe/tests/command/bump_command_test.rs b/crates/shirabe/tests/command/bump_command_test.rs
index 96c8baf..7accb75 100644
--- a/crates/shirabe/tests/command/bump_command_test.rs
+++ b/crates/shirabe/tests/command/bump_command_test.rs
@@ -4,7 +4,10 @@ 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)"]
+#[ignore = "needs create_installed_json / create_composer_lock helpers \
+ (InstalledFilesystemRepository / Locker::set_lock_data), and BumpCommand::execute calls \
+ require_composer -> Factory, reaching ProcessExecutor (git) -> shirabe-php-shim \
+ stream_set_blocking (stream.rs todo!(), requires fcntl(2))"]
#[test]
fn test_bump() {
todo!()
diff --git a/crates/shirabe/tests/command/check_platform_reqs_command_test.rs b/crates/shirabe/tests/command/check_platform_reqs_command_test.rs
index 9cbba58..47f89b9 100644
--- a/crates/shirabe/tests/command/check_platform_reqs_command_test.rs
+++ b/crates/shirabe/tests/command/check_platform_reqs_command_test.rs
@@ -1,19 +1,25 @@
//! ref: composer/tests/Composer/Test/Command/CheckPlatformReqsCommandTest.php
#[test]
-#[ignore = "missing TestCase::init_temp_composer, create_installed_json, create_composer_lock and get_application_tester (ApplicationTester) infrastructure"]
+#[ignore = "needs create_installed_json / create_composer_lock helpers (InstalledFilesystemRepository \
+ / Locker::set_lock_data), and require_composer -> Factory reaches ProcessExecutor (git) \
+ -> shirabe-php-shim stream_set_blocking (stream.rs todo!(), requires fcntl(2))"]
fn test_platform_reqs_are_satisfied() {
todo!()
}
#[test]
-#[ignore = "missing TestCase::init_temp_composer and get_application_tester (ApplicationTester) infrastructure"]
+#[ignore = "check-platform-reqs calls require_composer -> Factory, reaching ProcessExecutor (git) \
+ -> shirabe-php-shim stream_set_blocking (stream.rs todo!(), requires fcntl(2)) before \
+ the missing-lockfile LogicException can be thrown"]
fn test_exception_thrown_if_no_lockfile_found() {
todo!()
}
#[test]
-#[ignore = "missing TestCase::init_temp_composer, create_installed_json, create_composer_lock and get_application_tester (ApplicationTester) infrastructure"]
+#[ignore = "needs create_installed_json / create_composer_lock helpers (InstalledFilesystemRepository \
+ / Locker::set_lock_data), and require_composer -> Factory reaches ProcessExecutor (git) \
+ -> shirabe-php-shim stream_set_blocking (stream.rs todo!(), requires fcntl(2))"]
fn test_failed_platform_requirement() {
todo!()
}
diff --git a/crates/shirabe/tests/command/clear_cache_command_test.rs b/crates/shirabe/tests/command/clear_cache_command_test.rs
index 4ad04dd..b185579 100644
--- a/crates/shirabe/tests/command/clear_cache_command_test.rs
+++ b/crates/shirabe/tests/command/clear_cache_command_test.rs
@@ -1,6 +1,9 @@
//! ref: composer/tests/Composer/Test/Command/ClearCacheCommandTest.php
+use crate::test_case::{RunOptions, get_application_tester};
+use serial_test::serial;
use shirabe::util::platform::Platform;
+use shirabe_php_shim::PhpMixed;
fn tear_down() {
// --no-cache triggers the env to change so make sure the env is cleaned up after these tests run
@@ -16,25 +19,76 @@ impl Drop for TearDown {
}
#[test]
-#[ignore = "ApplicationTester / get_application_tester helper not implemented"]
+#[serial]
fn test_clear_cache_command_success() {
let _tear_down = TearDown;
- todo!()
+ let mut app_tester = get_application_tester();
+ app_tester
+ .run(
+ vec![(PhpMixed::from("command"), PhpMixed::from("clear-cache"))],
+ RunOptions::default(),
+ )
+ .unwrap();
+
+ assert_eq!(0, app_tester.get_status_code());
+
+ let output = app_tester.get_display();
+ assert!(
+ output.contains("All caches cleared."),
+ "expected output to contain 'All caches cleared.', got: {:?}",
+ output,
+ );
}
#[test]
-#[ignore = "ApplicationTester / get_application_tester helper not implemented"]
+#[serial]
fn test_clear_cache_command_with_option_garbage_collection() {
let _tear_down = TearDown;
- todo!()
+ let mut app_tester = get_application_tester();
+ app_tester
+ .run(
+ vec![
+ (PhpMixed::from("command"), PhpMixed::from("clear-cache")),
+ (PhpMixed::from("--gc"), PhpMixed::Bool(true)),
+ ],
+ RunOptions::default(),
+ )
+ .unwrap();
+
+ assert_eq!(0, app_tester.get_status_code());
+
+ let output = app_tester.get_display();
+ assert!(
+ output.contains("All caches garbage-collected."),
+ "expected output to contain 'All caches garbage-collected.', got: {:?}",
+ output,
+ );
}
#[test]
-#[ignore = "ApplicationTester / get_application_tester helper not implemented"]
+#[serial]
fn test_clear_cache_command_with_option_no_cache() {
let _tear_down = TearDown;
- todo!()
+ let mut app_tester = get_application_tester();
+ app_tester
+ .run(
+ vec![
+ (PhpMixed::from("command"), PhpMixed::from("clear-cache")),
+ (PhpMixed::from("--no-cache"), PhpMixed::Bool(true)),
+ ],
+ RunOptions::default(),
+ )
+ .unwrap();
+
+ assert_eq!(0, app_tester.get_status_code());
+
+ let output = app_tester.get_display();
+ assert!(
+ output.contains("Cache is not enabled"),
+ "expected output to contain 'Cache is not enabled', got: {:?}",
+ output,
+ );
}
diff --git a/crates/shirabe/tests/command/config_command_test.rs b/crates/shirabe/tests/command/config_command_test.rs
index 70e6a97..f43322d 100644
--- a/crates/shirabe/tests/command/config_command_test.rs
+++ b/crates/shirabe/tests/command/config_command_test.rs
@@ -1,31 +1,508 @@
//! ref: composer/tests/Composer/Test/Command/ConfigCommandTest.php
+use crate::test_case::{RunOptions, get_application_tester, init_temp_composer};
+use serial_test::serial;
+use shirabe_php_shim::PhpMixed;
+
+/// `['command' => 'config'] + $command`, with the command name prepended.
+fn config_input(command: Vec<(PhpMixed, PhpMixed)>) -> Vec<(PhpMixed, PhpMixed)> {
+ let mut input = vec![(PhpMixed::from("command"), PhpMixed::from("config"))];
+ input.extend(command);
+ input
+}
+
+fn key(setting_key: &str) -> (PhpMixed, PhpMixed) {
+ (PhpMixed::from("setting-key"), PhpMixed::from(setting_key))
+}
+
+fn value(values: &[&str]) -> (PhpMixed, PhpMixed) {
+ (
+ PhpMixed::from("setting-value"),
+ PhpMixed::List(values.iter().map(|v| PhpMixed::from(*v)).collect()),
+ )
+}
+
+fn flag(name: &str) -> (PhpMixed, PhpMixed) {
+ (PhpMixed::from(name), PhpMixed::Bool(true))
+}
+
+/// Reads CWD's composer.json as a `serde_json::Value` (mirrors PHP's `json_decode(..., true)`).
+fn read_composer_json() -> serde_json::Value {
+ let contents = std::fs::read_to_string("composer.json").unwrap();
+ serde_json::from_str(&contents).unwrap()
+}
+
+struct UpdateCase {
+ name: &'static str,
+ before: serde_json::Value,
+ command: Vec<(PhpMixed, PhpMixed)>,
+ expected: serde_json::Value,
+}
+
#[test]
-#[ignore = "requires ApplicationTester and TestCase::initTempComposer harness (not implemented)"]
+#[serial]
fn test_config_updates() {
- todo!()
+ let cases: Vec<UpdateCase> = vec![
+ UpdateCase {
+ name: "set scripts",
+ before: serde_json::json!({}),
+ command: vec![key("scripts.test"), value(&["foo bar"])],
+ expected: serde_json::json!({"scripts": {"test": "foo bar"}}),
+ },
+ UpdateCase {
+ name: "unset scripts",
+ before: serde_json::json!({"scripts": {"test": "foo bar", "lala": "baz"}}),
+ command: vec![key("scripts.lala"), flag("--unset")],
+ expected: serde_json::json!({"scripts": {"test": "foo bar"}}),
+ },
+ UpdateCase {
+ name: "set single config with bool normalizer",
+ before: serde_json::json!({}),
+ command: vec![key("use-github-api"), value(&["1"])],
+ expected: serde_json::json!({"config": {"use-github-api": true}}),
+ },
+ UpdateCase {
+ name: "set multi config",
+ before: serde_json::json!({}),
+ command: vec![key("github-protocols"), value(&["https", "git"])],
+ expected: serde_json::json!({"config": {"github-protocols": ["https", "git"]}}),
+ },
+ UpdateCase {
+ name: "set version",
+ before: serde_json::json!({}),
+ command: vec![key("version"), value(&["1.0.0"])],
+ expected: serde_json::json!({"version": "1.0.0"}),
+ },
+ UpdateCase {
+ name: "unset version",
+ before: serde_json::json!({"version": "1.0.0"}),
+ command: vec![key("version"), flag("--unset")],
+ expected: serde_json::json!({}),
+ },
+ UpdateCase {
+ name: "unset arbitrary property",
+ before: serde_json::json!({"random-prop": "1.0.0"}),
+ command: vec![key("random-prop"), flag("--unset")],
+ expected: serde_json::json!({}),
+ },
+ UpdateCase {
+ name: "set preferred-install",
+ before: serde_json::json!({}),
+ command: vec![key("preferred-install.foo/*"), value(&["source"])],
+ expected: serde_json::json!({"config": {"preferred-install": {"foo/*": "source"}}}),
+ },
+ UpdateCase {
+ name: "unset preferred-install",
+ before: serde_json::json!({"config": {"preferred-install": {"foo/*": "source"}}}),
+ command: vec![key("preferred-install.foo/*"), flag("--unset")],
+ expected: serde_json::json!({"config": {"preferred-install": {}}}),
+ },
+ UpdateCase {
+ name: "unset platform",
+ before: serde_json::json!({"config": {"platform": {"php": "7.2.5"}, "platform-check": false}}),
+ command: vec![key("platform.php"), flag("--unset")],
+ expected: serde_json::json!({"config": {"platform": {}, "platform-check": false}}),
+ },
+ UpdateCase {
+ name: "set extra with merge",
+ before: serde_json::json!({}),
+ command: vec![
+ key("extra.patches.foo/bar"),
+ value(&["{\"123\":\"value\"}"]),
+ flag("--json"),
+ flag("--merge"),
+ ],
+ expected: serde_json::json!({"extra": {"patches": {"foo/bar": {"123": "value"}}}}),
+ },
+ UpdateCase {
+ name: "combine extra with merge",
+ before: serde_json::json!({"extra": {"patches": {"foo/bar": {"5": "oldvalue"}}}}),
+ command: vec![
+ key("extra.patches.foo/bar"),
+ value(&["{\"123\":\"value\"}"]),
+ flag("--json"),
+ flag("--merge"),
+ ],
+ expected: serde_json::json!({"extra": {"patches": {"foo/bar": {"123": "value", "5": "oldvalue"}}}}),
+ },
+ UpdateCase {
+ name: "combine extra with list",
+ before: serde_json::json!({"extra": {"patches": {"foo/bar": ["oldvalue"]}}}),
+ command: vec![
+ key("extra.patches.foo/bar"),
+ value(&["{\"123\":\"value\"}"]),
+ flag("--json"),
+ flag("--merge"),
+ ],
+ expected: serde_json::json!({"extra": {"patches": {"foo/bar": {"123": "value", "0": "oldvalue"}}}}),
+ },
+ UpdateCase {
+ name: "overwrite extra with merge",
+ before: serde_json::json!({"extra": {"patches": {"foo/bar": {"123": "oldvalue"}}}}),
+ command: vec![
+ key("extra.patches.foo/bar"),
+ value(&["{\"123\":\"value\"}"]),
+ flag("--json"),
+ flag("--merge"),
+ ],
+ expected: serde_json::json!({"extra": {"patches": {"foo/bar": {"123": "value"}}}}),
+ },
+ UpdateCase {
+ name: "unset autoload",
+ before: serde_json::json!({"autoload": {"psr-4": ["test"], "classmap": ["test"]}}),
+ command: vec![key("autoload.psr-4"), flag("--unset")],
+ expected: serde_json::json!({"autoload": {"classmap": ["test"]}}),
+ },
+ UpdateCase {
+ name: "unset autoload-dev",
+ before: serde_json::json!({"autoload-dev": {"psr-4": ["test"], "classmap": ["test"]}}),
+ command: vec![key("autoload-dev.psr-4"), flag("--unset")],
+ expected: serde_json::json!({"autoload-dev": {"classmap": ["test"]}}),
+ },
+ UpdateCase {
+ name: "set audit.ignore-unreachable",
+ before: serde_json::json!({}),
+ command: vec![key("audit.ignore-unreachable"), value(&["true"])],
+ expected: serde_json::json!({"config": {"audit": {"ignore-unreachable": true}}}),
+ },
+ UpdateCase {
+ name: "set audit.block-insecure",
+ before: serde_json::json!({}),
+ command: vec![key("audit.block-insecure"), value(&["false"])],
+ expected: serde_json::json!({"config": {"audit": {"block-insecure": false}}}),
+ },
+ UpdateCase {
+ name: "set audit.block-abandoned",
+ before: serde_json::json!({}),
+ command: vec![key("audit.block-abandoned"), value(&["true"])],
+ expected: serde_json::json!({"config": {"audit": {"block-abandoned": true}}}),
+ },
+ UpdateCase {
+ name: "unset audit.ignore-unreachable",
+ before: serde_json::json!({"config": {"audit": {"ignore-unreachable": true}}}),
+ command: vec![key("audit.ignore-unreachable"), flag("--unset")],
+ expected: serde_json::json!({"config": {"audit": {}}}),
+ },
+ UpdateCase {
+ name: "set audit.ignore-severity",
+ before: serde_json::json!({}),
+ command: vec![key("audit.ignore-severity"), value(&["low", "medium"])],
+ expected: serde_json::json!({"config": {"audit": {"ignore-severity": ["low", "medium"]}}}),
+ },
+ UpdateCase {
+ name: "set audit.ignore as array",
+ before: serde_json::json!({}),
+ command: vec![
+ key("audit.ignore"),
+ value(&["[\"CVE-2024-1234\",\"GHSA-xxxx-yyyy\"]"]),
+ flag("--json"),
+ ],
+ expected: serde_json::json!({"config": {"audit": {"ignore": ["CVE-2024-1234", "GHSA-xxxx-yyyy"]}}}),
+ },
+ UpdateCase {
+ name: "set audit.ignore as object",
+ before: serde_json::json!({}),
+ command: vec![
+ key("audit.ignore"),
+ value(&[
+ "{\"CVE-2024-1234\":\"False positive\",\"GHSA-xxxx-yyyy\":\"Not applicable\"}",
+ ]),
+ flag("--json"),
+ ],
+ expected: serde_json::json!({"config": {"audit": {"ignore": {"CVE-2024-1234": "False positive", "GHSA-xxxx-yyyy": "Not applicable"}}}}),
+ },
+ UpdateCase {
+ name: "merge audit.ignore array",
+ before: serde_json::json!({"config": {"audit": {"ignore": ["CVE-2024-1234"]}}}),
+ command: vec![
+ key("audit.ignore"),
+ value(&["[\"CVE-2024-5678\"]"]),
+ flag("--json"),
+ flag("--merge"),
+ ],
+ expected: serde_json::json!({"config": {"audit": {"ignore": ["CVE-2024-1234", "CVE-2024-5678"]}}}),
+ },
+ UpdateCase {
+ name: "merge audit.ignore object",
+ before: serde_json::json!({"config": {"audit": {"ignore": {"CVE-2024-1234": "Old reason"}}}}),
+ command: vec![
+ key("audit.ignore"),
+ value(&["{\"CVE-2024-5678\":\"New advisory\"}"]),
+ flag("--json"),
+ flag("--merge"),
+ ],
+ expected: serde_json::json!({"config": {"audit": {"ignore": {"CVE-2024-5678": "New advisory", "CVE-2024-1234": "Old reason"}}}}),
+ },
+ UpdateCase {
+ name: "overwrite audit.ignore key with merge",
+ before: serde_json::json!({"config": {"audit": {"ignore": {"CVE-2024-1234": "Old reason"}}}}),
+ command: vec![
+ key("audit.ignore"),
+ value(&["{\"CVE-2024-1234\":\"New reason\"}"]),
+ flag("--json"),
+ flag("--merge"),
+ ],
+ expected: serde_json::json!({"config": {"audit": {"ignore": {"CVE-2024-1234": "New reason"}}}}),
+ },
+ UpdateCase {
+ name: "set audit.ignore-abandoned as array",
+ before: serde_json::json!({}),
+ command: vec![
+ key("audit.ignore-abandoned"),
+ value(&["[\"vendor/package1\",\"vendor/package2\"]"]),
+ flag("--json"),
+ ],
+ expected: serde_json::json!({"config": {"audit": {"ignore-abandoned": ["vendor/package1", "vendor/package2"]}}}),
+ },
+ UpdateCase {
+ name: "set audit.ignore-abandoned as object",
+ before: serde_json::json!({}),
+ command: vec![
+ key("audit.ignore-abandoned"),
+ value(&[
+ "{\"vendor/package1\":\"Still maintained\",\"vendor/package2\":\"Fork available\"}",
+ ]),
+ flag("--json"),
+ ],
+ expected: serde_json::json!({"config": {"audit": {"ignore-abandoned": {"vendor/package1": "Still maintained", "vendor/package2": "Fork available"}}}}),
+ },
+ UpdateCase {
+ name: "merge audit.ignore-abandoned array",
+ before: serde_json::json!({"config": {"audit": {"ignore-abandoned": ["vendor/package1"]}}}),
+ command: vec![
+ key("audit.ignore-abandoned"),
+ value(&["[\"vendor/package2\"]"]),
+ flag("--json"),
+ flag("--merge"),
+ ],
+ expected: serde_json::json!({"config": {"audit": {"ignore-abandoned": ["vendor/package1", "vendor/package2"]}}}),
+ },
+ UpdateCase {
+ name: "merge audit.ignore-abandoned object",
+ before: serde_json::json!({"config": {"audit": {"ignore-abandoned": {"vendor/package1": "Old reason"}}}}),
+ command: vec![
+ key("audit.ignore-abandoned"),
+ value(&["{\"vendor/package2\":\"New reason\"}"]),
+ flag("--json"),
+ flag("--merge"),
+ ],
+ expected: serde_json::json!({"config": {"audit": {"ignore-abandoned": {"vendor/package2": "New reason", "vendor/package1": "Old reason"}}}}),
+ },
+ UpdateCase {
+ name: "unset audit.ignore",
+ before: serde_json::json!({"config": {"audit": {"ignore": ["CVE-2024-1234"]}}}),
+ command: vec![key("audit.ignore"), flag("--unset")],
+ expected: serde_json::json!({"config": {"audit": {}}}),
+ },
+ UpdateCase {
+ name: "unset audit.ignore-abandoned",
+ before: serde_json::json!({"config": {"audit": {"ignore-abandoned": ["vendor/package1"]}}}),
+ command: vec![key("audit.ignore-abandoned"), flag("--unset")],
+ expected: serde_json::json!({"config": {"audit": {}}}),
+ },
+ ];
+
+ for case in cases {
+ let _tear_down = init_temp_composer(Some(&case.before), None, None, false);
+
+ let mut app_tester = get_application_tester();
+ app_tester
+ .run(config_input(case.command), RunOptions::default())
+ .unwrap_or_else(|e| panic!("case {:?}: run failed: {:?}", case.name, e));
+
+ assert_eq!(
+ 0,
+ app_tester.get_status_code(),
+ "case {:?}: display: {}",
+ case.name,
+ app_tester.get_display()
+ );
+
+ assert_eq!(case.expected, read_composer_json(), "case {:?}", case.name);
+ }
+}
+
+struct ReadCase {
+ name: &'static str,
+ composer_json: serde_json::Value,
+ command: Vec<(PhpMixed, PhpMixed)>,
+ expected: &'static str,
}
#[test]
-#[ignore = "requires ApplicationTester and TestCase::initTempComposer harness (not implemented)"]
+#[serial]
fn test_config_reads() {
- todo!()
+ let cases: Vec<ReadCase> = vec![
+ ReadCase {
+ name: "read description",
+ composer_json: serde_json::json!({"description": "foo bar"}),
+ command: vec![key("description")],
+ expected: "foo bar",
+ },
+ ReadCase {
+ name: "read vendor-dir with source",
+ composer_json: serde_json::json!({"config": {"vendor-dir": "lala"}}),
+ command: vec![key("vendor-dir"), flag("--source")],
+ expected: "lala (./composer.json)",
+ },
+ ReadCase {
+ name: "read default vendor-dir",
+ composer_json: serde_json::json!({}),
+ command: vec![key("vendor-dir")],
+ expected: "vendor",
+ },
+ ReadCase {
+ name: "read repos by named key",
+ composer_json: serde_json::json!({"repositories": {"foo": {"type": "vcs", "url": "https://example.org"}, "packagist.org": {"type": "composer", "url": "https://repo.packagist.org"}}}),
+ command: vec![key("repositories.foo")],
+ expected: "{\"type\":\"vcs\",\"url\":\"https://example.org\"}",
+ },
+ ReadCase {
+ name: "read all repos includes the default packagist",
+ composer_json: serde_json::json!({"repositories": {"foo": {"type": "vcs", "url": "https://example.org"}, "packagist.org": {"type": "composer", "url": "https://repo.packagist.org"}}}),
+ command: vec![key("repos")],
+ expected: "{\"foo\":{\"type\":\"vcs\",\"url\":\"https://example.org\"},\"packagist.org\":{\"type\":\"composer\",\"url\":\"https://repo.packagist.org\"}}",
+ },
+ ReadCase {
+ name: "read all repos does not include the disabled packagist",
+ composer_json: serde_json::json!({"repositories": {"foo": {"type": "vcs", "url": "https://example.org"}, "packagist.org": false}}),
+ command: vec![key("repos")],
+ expected: "{\"foo\":{\"type\":\"vcs\",\"url\":\"https://example.org\"}}",
+ },
+ ];
+
+ for case in cases {
+ let _tear_down = init_temp_composer(Some(&case.composer_json), None, None, false);
+
+ let mut app_tester = get_application_tester();
+ app_tester
+ .run(config_input(case.command), RunOptions::default())
+ .unwrap_or_else(|e| panic!("case {:?}: run failed: {:?}", case.name, e));
+
+ assert_eq!(0, app_tester.get_status_code(), "case {:?}", case.name);
+
+ assert_eq!(
+ case.expected,
+ app_tester.get_display().trim(),
+ "case {:?}",
+ case.name
+ );
+
+ // The composer.json should not be modified by config reads.
+ assert_eq!(
+ case.composer_json,
+ read_composer_json(),
+ "case {:?}: composer.json must not be modified by config reads",
+ case.name
+ );
+ }
}
+/// ref: provideConfigReads 'read repos by numeric index'.
+///
+/// Split out from the rest because list-form `repositories` makes `Config::all()` normalize the
+/// repository through RepositoryFactory, which instantiates a repository and reaches
+/// shirabe-php-shim stream_set_blocking (stream.rs todo!(), requires fcntl(2)).
#[test]
-#[ignore = "requires ApplicationTester (getApplicationTester) harness (not implemented)"]
+#[serial]
+#[ignore = "list-form repositories drive Config::all -> RepositoryFactory, reaching \
+ shirabe-php-shim stream_set_blocking (stream.rs todo!(), requires fcntl(2))"]
+fn test_config_reads_repos_by_numeric_index() {
+ let composer_json = serde_json::json!({"repositories": [{"type": "vcs", "url": "https://example.org"}, {"type": "composer", "url": "https://repo.packagist.org"}]});
+ let _tear_down = init_temp_composer(Some(&composer_json), None, None, false);
+
+ let mut app_tester = get_application_tester();
+ app_tester
+ .run(config_input(vec![key("repos.0")]), RunOptions::default())
+ .unwrap();
+
+ assert_eq!(0, app_tester.get_status_code());
+ assert_eq!(
+ "{\"type\":\"vcs\",\"url\":\"https://example.org\"}",
+ app_tester.get_display().trim()
+ );
+}
+
+#[test]
+#[serial]
fn test_config_throws_for_invalid_arg_combination() {
- todo!()
+ let mut app_tester = get_application_tester();
+ let result = app_tester.run(
+ config_input(vec![
+ (
+ PhpMixed::from("--file"),
+ PhpMixed::from("alt.composer.json"),
+ ),
+ flag("--global"),
+ ]),
+ RunOptions::default(),
+ );
+
+ let err = result.expect_err("expected RuntimeException");
+ assert!(
+ err.to_string()
+ .contains("--file and --global can not be combined"),
+ "got: {:?}",
+ err
+ );
}
#[test]
-#[ignore = "requires ApplicationTester and TestCase::initTempComposer harness (not implemented)"]
+#[serial]
+#[ignore = "the command correctly throws, but Application::do_run's exception path calls \
+ hint_common_errors -> get_composer (a temp composer.json exists here), which reaches \
+ ProcessExecutor (git) -> shirabe-php-shim stream_set_blocking (stream.rs todo!(), \
+ requires fcntl(2))"]
fn test_config_throws_for_invalid_severity() {
- todo!()
+ let _tear_down = init_temp_composer(Some(&serde_json::json!({})), None, None, false);
+
+ let mut app_tester = get_application_tester();
+ let result = app_tester.run(
+ config_input(vec![
+ key("audit.ignore-severity"),
+ value(&["low", "invalid"]),
+ ]),
+ RunOptions::default(),
+ );
+
+ let err = result.expect_err("expected RuntimeException");
+ assert!(
+ err.to_string()
+ .contains("valid severities include: low, medium, high, critical"),
+ "got: {:?}",
+ err
+ );
}
#[test]
-#[ignore = "requires ApplicationTester and TestCase::initTempComposer harness (not implemented)"]
+#[serial]
+#[ignore = "the command correctly throws, but Application::do_run's exception path calls \
+ hint_common_errors -> get_composer (a temp composer.json exists here), which reaches \
+ ProcessExecutor (git) -> shirabe-php-shim stream_set_blocking (stream.rs todo!(), \
+ requires fcntl(2))"]
fn test_config_throws_when_merging_array_with_object() {
- todo!()
+ let _tear_down = init_temp_composer(
+ Some(&serde_json::json!({"config": {"audit": {"ignore": ["CVE-2024-1234"]}}})),
+ None,
+ None,
+ false,
+ );
+
+ let mut app_tester = get_application_tester();
+ let result = app_tester.run(
+ config_input(vec![
+ key("audit.ignore"),
+ value(&["{\"CVE-2024-5678\":\"reason\"}"]),
+ flag("--json"),
+ flag("--merge"),
+ ]),
+ RunOptions::default(),
+ );
+
+ let err = result.expect_err("expected RuntimeException");
+ assert!(
+ err.to_string().contains("Cannot merge array and object"),
+ "got: {:?}",
+ err
+ );
}
diff --git a/crates/shirabe/tests/command/diagnose_command_test.rs b/crates/shirabe/tests/command/diagnose_command_test.rs
index a5a6c8b..a8e6c16 100644
--- a/crates/shirabe/tests/command/diagnose_command_test.rs
+++ b/crates/shirabe/tests/command/diagnose_command_test.rs
@@ -1,13 +1,15 @@
//! ref: composer/tests/Composer/Test/Command/DiagnoseCommandTest.php
#[test]
-#[ignore = "missing TestCase::init_temp_composer and get_application_tester (ApplicationTester) infrastructure"]
+#[ignore = "diagnose checks live http/https connectivity to packagist and the github.com rate \
+ limit, so it requires real network access"]
fn test_cmd_fail() {
todo!()
}
#[test]
-#[ignore = "missing TestCase::init_temp_composer and get_application_tester (ApplicationTester) infrastructure"]
+#[ignore = "diagnose checks live http/https connectivity to packagist and the github.com rate \
+ limit, so it requires real network access"]
fn test_cmd_success() {
todo!()
}
diff --git a/crates/shirabe/tests/command/home_command_test.rs b/crates/shirabe/tests/command/home_command_test.rs
index b8b67d7..2c8cb6b 100644
--- a/crates/shirabe/tests/command/home_command_test.rs
+++ b/crates/shirabe/tests/command/home_command_test.rs
@@ -1,7 +1,10 @@
//! ref: composer/tests/Composer/Test/Command/HomeCommandTest.php
#[test]
-#[ignore = "init_temp_composer / create_installed_json / get_application_tester (ApplicationTester) helpers not implemented"]
+#[ignore = "needs create_installed_json helper (InstalledFilesystemRepository) plus \
+ setHomepage on CompletePackage, and HomeCommand::initialize_repos calls try_composer \
+ -> Factory, reaching ProcessExecutor (git) -> shirabe-php-shim stream_set_blocking \
+ (stream.rs todo!(), requires fcntl(2))"]
fn test_home_command_with_show_flag() {
todo!()
}
diff --git a/crates/shirabe/tests/command/validate_command_test.rs b/crates/shirabe/tests/command/validate_command_test.rs
index b306100..31af4be 100644
--- a/crates/shirabe/tests/command/validate_command_test.rs
+++ b/crates/shirabe/tests/command/validate_command_test.rs
@@ -1,25 +1,167 @@
//! ref: composer/tests/Composer/Test/Command/ValidateCommandTest.php
+use crate::test_case::{RunOptions, get_application_tester, init_temp_composer};
+use serial_test::serial;
+use shirabe::util::platform::Platform;
+use shirabe_php_shim::PhpMixed;
+
+/// ref: ValidateCommandTest::MINIMAL_VALID_CONFIGURATION
+fn minimal_valid_configuration() -> serde_json::Value {
+ serde_json::json!({
+ "name": "test/suite",
+ "type": "library",
+ "description": "A generical test suite",
+ "license": "MIT",
+ "repositories": {
+ "packages": {
+ "type": "package",
+ "package": [
+ {"name": "root/req", "version": "1.0.0", "require": {"dep/pkg": "^1"}},
+ {"name": "dep/pkg", "version": "1.0.0"},
+ {"name": "dep/pkg", "version": "1.0.1"},
+ {"name": "dep/pkg", "version": "1.0.2"}
+ ]
+ }
+ },
+ "require": {
+ "root/req": "1.*"
+ }
+ })
+}
+
+fn validate_input(command: Vec<(PhpMixed, PhpMixed)>) -> Vec<(PhpMixed, PhpMixed)> {
+ let mut input = vec![(PhpMixed::from("command"), PhpMixed::from("validate"))];
+ input.extend(command);
+ input
+}
+
+struct ValidateCase {
+ name: &'static str,
+ composer_json: serde_json::Value,
+ command: Vec<(PhpMixed, PhpMixed)>,
+ expected: &'static str,
+}
+
+/// ref: provideValidateTests
+fn provide_validate_tests() -> Vec<ValidateCase> {
+ // $publishDataStripped = array_diff_key(MINIMAL_VALID_CONFIGURATION, ['name','type','description','license'])
+ let publish_data_stripped = serde_json::json!({
+ "repositories": {
+ "packages": {
+ "type": "package",
+ "package": [
+ {"name": "root/req", "version": "1.0.0", "require": {"dep/pkg": "^1"}},
+ {"name": "dep/pkg", "version": "1.0.0"},
+ {"name": "dep/pkg", "version": "1.0.1"},
+ {"name": "dep/pkg", "version": "1.0.2"}
+ ]
+ }
+ },
+ "require": {
+ "root/req": "1.*"
+ }
+ });
+
+ vec![
+ ValidateCase {
+ name: "validation passing",
+ composer_json: minimal_valid_configuration(),
+ command: vec![],
+ expected: "<warning>Composer could not detect the root package (test/suite) version, defaulting to '1.0.0'. See https://getcomposer.org/root-version</warning>\n<warning>Composer could not detect the root package (test/suite) version, defaulting to '1.0.0'. See https://getcomposer.org/root-version</warning>\n./composer.json is valid",
+ },
+ ValidateCase {
+ name: "passing but with warnings",
+ composer_json: publish_data_stripped.clone(),
+ command: vec![],
+ expected: "./composer.json is valid for simple usage with Composer but has\nstrict errors that make it unable to be published as a package\n<warning>See https://getcomposer.org/doc/04-schema.md for details on the schema</warning>\n# Publish errors\n- name : The property name is required\n- description : The property description is required\n<warning># General warnings</warning>\n- No license specified, it is recommended to do so. For closed-source software you may use \"proprietary\" as license.",
+ },
+ ValidateCase {
+ name: "passing without publish-check",
+ composer_json: publish_data_stripped,
+ command: vec![(PhpMixed::from("--no-check-publish"), PhpMixed::Bool(true))],
+ expected: "./composer.json is valid, but with a few warnings\n<warning>See https://getcomposer.org/doc/04-schema.md for details on the schema</warning>\n<warning># General warnings</warning>\n- No license specified, it is recommended to do so. For closed-source software you may use \"proprietary\" as license.",
+ },
+ ]
+}
+
#[test]
-#[ignore = "missing TestCase::init_temp_composer and get_application_tester (ApplicationTester with run/get_display) infrastructure"]
+#[serial]
+#[ignore = "validate creates a Composer instance (create_composer_instance -> Factory) which \
+ reaches ProcessExecutor (git) -> shirabe-php-shim stream_set_blocking (stream.rs \
+ todo!(), requires fcntl(2))"]
fn test_validate() {
- todo!()
+ for case in provide_validate_tests() {
+ let _tear_down = init_temp_composer(Some(&case.composer_json), None, None, true);
+
+ let mut app_tester = get_application_tester();
+ app_tester
+ .run(validate_input(case.command), RunOptions::default())
+ .unwrap_or_else(|e| panic!("case {:?}: run failed: {:?}", case.name, e));
+
+ assert_eq!(
+ case.expected.trim(),
+ app_tester.get_display().trim(),
+ "case {:?}",
+ case.name
+ );
+ }
}
#[test]
-#[ignore = "missing TestCase::init_temp_composer and get_application_tester (ApplicationTester with run/get_display) infrastructure"]
+#[serial]
fn test_validate_on_file_issues() {
- todo!()
+ let tear_down = init_temp_composer(Some(&minimal_valid_configuration()), None, None, true);
+ std::fs::remove_file(tear_down.working_dir().join("composer.json")).unwrap();
+
+ let mut app_tester = get_application_tester();
+ app_tester
+ .run(validate_input(vec![]), RunOptions::default())
+ .unwrap();
+
+ assert_eq!(
+ "./composer.json not found.",
+ app_tester.get_display().trim()
+ );
+
+ drop(tear_down);
}
#[test]
-#[ignore = "missing TestCase::init_temp_composer / create_composer_lock and get_application_tester (ApplicationTester) infrastructure"]
+#[serial]
+#[ignore = "validate with a lock file creates a Composer instance and queries the Locker, reaching \
+ ProcessExecutor (git) -> shirabe-php-shim stream_set_blocking (stream.rs todo!(), \
+ requires fcntl(2))"]
fn test_with_composer_lock() {
todo!()
}
#[test]
-#[ignore = "missing TestCase::init_temp_composer and get_application_tester (ApplicationTester with run/get_display/get_status_code) infrastructure"]
+#[serial]
fn test_unaccessible_file() {
- todo!()
+ if Platform::is_windows() {
+ // ref: $this->markTestSkipped('Does not run on windows');
+ return;
+ }
+ 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(&minimal_valid_configuration()), None, None, true);
+ let composer_json = tear_down.working_dir().join("composer.json");
+ shirabe_php_shim::chmod(&composer_json.to_string_lossy(), 0o200);
+
+ let mut app_tester = get_application_tester();
+ app_tester
+ .run(validate_input(vec![]), RunOptions::default())
+ .unwrap();
+
+ assert_eq!(
+ "./composer.json is not readable.",
+ app_tester.get_display().trim()
+ );
+ assert_eq!(3, app_tester.get_status_code());
+
+ shirabe_php_shim::chmod(&composer_json.to_string_lossy(), 0o700);
+ drop(tear_down);
}
diff --git a/crates/shirabe/tests/package/loader/root_package_loader_test.rs b/crates/shirabe/tests/package/loader/root_package_loader_test.rs
index e78967e..23c8beb 100644
--- a/crates/shirabe/tests/package/loader/root_package_loader_test.rs
+++ b/crates/shirabe/tests/package/loader/root_package_loader_test.rs
@@ -36,7 +36,7 @@ fn http_downloader(
}
#[test]
-#[ignore]
+#[ignore = "process_executor.enable_async() drives the async stream path, which calls stream_set_blocking (fcntl(2) todo!() in shirabe-php-shim::stream)"]
fn test_stability_flags_parsing() {
let io = null_io();
let config = Rc::new(RefCell::new(Config::new(true, None)));
diff --git a/crates/shirabe/tests/package/loader/validating_array_loader_test.rs b/crates/shirabe/tests/package/loader/validating_array_loader_test.rs
index db5ff07..2e614ee 100644
--- a/crates/shirabe/tests/package/loader/validating_array_loader_test.rs
+++ b/crates/shirabe/tests/package/loader/validating_array_loader_test.rs
@@ -367,7 +367,6 @@ fn success_provider() -> Vec<IndexMap<String, PhpMixed>> {
}
/// ref: ValidatingArrayLoaderTest::testLoadSuccess
-#[ignore]
#[test]
fn test_load_success() {
for cfg in success_provider() {
@@ -795,7 +794,6 @@ fn error_provider() -> Vec<(IndexMap<String, PhpMixed>, Vec<String>)> {
}
/// ref: ValidatingArrayLoaderTest::testLoadFailureThrowsException
-#[ignore]
#[test]
fn test_load_failure_throws_exception() {
for (cfg, mut expected_errors) in error_provider() {
@@ -977,7 +975,7 @@ fn warning_provider() -> Vec<(
}
/// ref: ValidatingArrayLoaderTest::testLoadWarnings
-#[ignore]
+#[ignore = "license warning cases need the SPDX license-expression grammar (recursive PCRE), not yet ported: spdx_licenses todo!()"]
#[test]
fn test_load_warnings() {
for (cfg, mut expected_warnings, _must_check, _expected_array) in warning_provider() {
@@ -999,7 +997,7 @@ fn test_load_warnings() {
}
/// ref: ValidatingArrayLoaderTest::testLoadSkipsWarningDataWhenIgnoringErrors
-#[ignore]
+#[ignore = "must_check license cases need the SPDX license-expression grammar (recursive PCRE), not yet ported: spdx_licenses todo!()"]
#[test]
fn test_load_skips_warning_data_when_ignoring_errors() {
for (mut cfg, _expected_warnings, must_check, expected_array) in warning_provider() {
diff --git a/crates/shirabe/tests/package/root_alias_package_test.rs b/crates/shirabe/tests/package/root_alias_package_test.rs
index c275da3..41b1c40 100644
--- a/crates/shirabe/tests/package/root_alias_package_test.rs
+++ b/crates/shirabe/tests/package/root_alias_package_test.rs
@@ -37,7 +37,6 @@ fn test_update_requires() {
}
#[test]
-#[ignore]
fn test_update_dev_requires() {
let alias = alias();
assert!(alias.get_dev_requires().is_empty());
@@ -46,7 +45,6 @@ fn test_update_dev_requires() {
}
#[test]
-#[ignore]
fn test_update_conflicts() {
let alias = alias();
assert!(alias.get_conflicts().is_empty());
@@ -55,7 +53,6 @@ fn test_update_conflicts() {
}
#[test]
-#[ignore]
fn test_update_provides() {
let alias = alias();
assert!(alias.get_provides().is_empty());
@@ -64,7 +61,6 @@ fn test_update_provides() {
}
#[test]
-#[ignore]
fn test_update_replaces() {
let alias = alias();
assert!(alias.get_replaces().is_empty());
diff --git a/crates/shirabe/tests/repository/artifact_repository_test.rs b/crates/shirabe/tests/repository/artifact_repository_test.rs
index 63fd238..90e5625 100644
--- a/crates/shirabe/tests/repository/artifact_repository_test.rs
+++ b/crates/shirabe/tests/repository/artifact_repository_test.rs
@@ -35,7 +35,7 @@ fn create_repo(url: &str) -> ArtifactRepository {
}
#[test]
-#[ignore]
+#[ignore = "the artifacts fixtures dir contains a .tar file (jsonInRootTarFile); scanning it routes through Tar::get_composer_json -> PharData::new which is todo!()"]
fn test_extracts_configs_from_zip_archives() {
if set_up() {
return;
@@ -85,7 +85,7 @@ fn test_extracts_configs_from_zip_archives() {
}
#[test]
-#[ignore]
+#[ignore = "the artifacts fixtures dir contains a .tar file (jsonInRootTarFile); scanning it routes through Tar::get_composer_json -> PharData::new which is todo!()"]
fn test_absolute_repo_url_creates_absolute_url_packages() {
if set_up() {
return;
@@ -106,7 +106,7 @@ fn test_absolute_repo_url_creates_absolute_url_packages() {
}
#[test]
-#[ignore]
+#[ignore = "the relative url is resolved from the process cwd (the crate manifest dir under cargo, not the composer test root), so the artifacts dir is not found; additionally the dir contains a .tar file routing through PharData::new which is todo!()"]
fn test_relative_repo_url_creates_relative_url_packages() {
if set_up() {
return;
diff --git a/crates/shirabe/tests/repository/path_repository_test.rs b/crates/shirabe/tests/repository/path_repository_test.rs
index 62abbfa..5679c38 100644
--- a/crates/shirabe/tests/repository/path_repository_test.rs
+++ b/crates/shirabe/tests/repository/path_repository_test.rs
@@ -68,7 +68,7 @@ fn test_load_package_from_file_system_with_version() {
);
}
-#[ignore]
+#[ignore = "version guessing for an unversioned package calls VersionGuesser::guess_git_version, which reaches stream_set_blocking (fcntl(2) not implemented, todo!()) and aborts the process"]
#[test]
fn test_load_package_from_file_system_without_version() {
let repository_url = [
@@ -90,7 +90,7 @@ fn test_load_package_from_file_system_without_version() {
assert!(!package_version.is_empty());
}
-#[ignore]
+#[ignore = "the without-version fixture matched by the wildcard triggers VersionGuesser::guess_git_version, which reaches stream_set_blocking (fcntl(2) not implemented, todo!()) and aborts the process"]
#[test]
fn test_load_package_from_file_system_with_wildcard() {
let repository_url =
@@ -160,7 +160,7 @@ fn test_load_package_with_explicit_versions() {
}
/// Verify relative repository URLs remain relative, see #4439
-#[ignore]
+#[ignore = "relies on the process cwd being the test's __DIR__ (the Repository fixtures dir) so the computed relative url resolves; under cargo the cwd is the crate manifest dir, so the relative url does not point at the fixture and getPackages errors"]
#[test]
fn test_url_remains_relative() {
// realpath() does not fully expand the paths
@@ -197,7 +197,7 @@ fn test_url_remains_relative() {
assert_eq!(Some(relative_url), package.get_dist_url());
}
-#[ignore]
+#[ignore = "the wildcard url also matches the without-version fixture, whose version guessing reaches stream_set_blocking (fcntl(2) not implemented, todo!()) and aborts the process"]
#[test]
fn test_reference_none() {
let options = coordinates(vec![("reference", PhpMixed::String("none".to_string()))]);
@@ -216,7 +216,7 @@ fn test_reference_none() {
}
}
-#[ignore]
+#[ignore = "the wildcard url also matches the without-version fixture, whose version guessing reaches stream_set_blocking (fcntl(2) not implemented, todo!()) and aborts the process"]
#[test]
fn test_reference_config() {
let options = coordinates(vec![
diff --git a/crates/shirabe/tests/repository/repository_factory_test.rs b/crates/shirabe/tests/repository/repository_factory_test.rs
index c018c45..b1a9001 100644
--- a/crates/shirabe/tests/repository/repository_factory_test.rs
+++ b/crates/shirabe/tests/repository/repository_factory_test.rs
@@ -12,7 +12,6 @@ use shirabe::util::http_downloader::HttpDownloader;
use shirabe_php_shim::PhpMixed;
#[test]
-#[ignore]
fn test_manager_with_all_repository_types() {
let io: Rc<RefCell<dyn IOInterface>> = Rc::new(RefCell::new(NullIO::new()));
let config = Rc::new(RefCell::new(Config::new(false, None)));
@@ -97,7 +96,6 @@ fn generate_repository_name_provider() -> Vec<(
}
#[test]
-#[ignore]
fn test_generate_repository_name() {
for (index, repo_pairs, existing_keys, expected) in generate_repository_name_provider() {
let repo: IndexMap<String, PhpMixed> = repo_pairs
diff --git a/crates/shirabe/tests/repository/repository_manager_test.rs b/crates/shirabe/tests/repository/repository_manager_test.rs
index bb85d6a..18f4a7a 100644
--- a/crates/shirabe/tests/repository/repository_manager_test.rs
+++ b/crates/shirabe/tests/repository/repository_manager_test.rs
@@ -72,7 +72,6 @@ fn str_config(pairs: &[(&str, PhpMixed)]) -> IndexMap<String, PhpMixed> {
}
#[test]
-#[ignore]
fn test_prepend() {
let SetUp { tmpdir } = set_up();
let _tear_down = TearDown::new(tmpdir.path().to_path_buf());
@@ -90,7 +89,7 @@ fn test_prepend() {
}
#[test]
-#[ignore]
+#[ignore = "create_repository routes to RepositoryManager::create_repository_by_class, which is todo!() (dynamic instantiation by class name not yet ported)"]
fn test_repo_creation() {
let SetUp { tmpdir } = set_up();
let _tear_down = TearDown::new(tmpdir.path().to_path_buf());
@@ -176,7 +175,7 @@ fn test_repo_creation() {
}
#[test]
-#[ignore]
+#[ignore = "the first case (\"pear\", a registered type) reaches RepositoryManager::create_repository_by_class, which is todo!() (dynamic instantiation by class name not yet ported), before the \"invalid\" case can be checked"]
fn test_invalid_repo_creation_throws() {
let SetUp { tmpdir } = set_up();
let _tear_down = TearDown::new(tmpdir.path().to_path_buf());
diff --git a/crates/shirabe/tests/util/forgejo_test.rs b/crates/shirabe/tests/util/forgejo_test.rs
index e8bf766..fd6dbc9 100644
--- a/crates/shirabe/tests/util/forgejo_test.rs
+++ b/crates/shirabe/tests/util/forgejo_test.rs
@@ -1,17 +1,186 @@
//! ref: composer/tests/Composer/Test/Util/ForgejoTest.php
-// Both cases construct Forgejo with a mocked IO/Config/JsonConfigSource and a mocked
-// HttpDownloader to drive the username/password authentication flow. Mocking is not
-// available, and a real HttpDownloader reaches curl_multi_init (todo!()).
+use std::cell::RefCell;
+use std::rc::Rc;
+
+use shirabe::config::{Config, ConfigSourceInterface};
+use shirabe::io::IOInterface;
+use shirabe::io::io_interface;
+use shirabe::util::Forgejo;
+use shirabe::util::http_downloader::{HttpDownloader, HttpDownloaderMockHandler};
+use shirabe_php_shim::PhpMixed;
+
+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};
+
+const USERNAME: &str = "username";
+const ACCESS_TOKEN: &str = "access-token";
+const MESSAGE: &str = "mymessage";
+const ORIGIN: &str = "codeberg.org";
+
+// Records the config setting names a source has had removed, plus a fixed getName,
+// mirroring ForgejoTest's JsonConfigSource mocks (getName -> "auth.json", and the
+// config source stubbing removeConfigSetting('forgejo-token.<origin>')).
+#[derive(Debug)]
+struct ConfigSourceMock {
+ name: String,
+ removed: Rc<RefCell<Vec<String>>>,
+}
+
+impl ConfigSourceMock {
+ fn new(name: &str) -> (Box<Self>, Rc<RefCell<Vec<String>>>) {
+ let removed = Rc::new(RefCell::new(Vec::new()));
+ (
+ Box::new(Self {
+ name: name.to_string(),
+ removed: removed.clone(),
+ }),
+ removed,
+ )
+ }
+}
+
+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<()> {
+ Ok(())
+ }
+ fn remove_config_setting(&mut self, name: &str) -> anyhow::Result<()> {
+ self.removed.borrow_mut().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()
+ }
+}
+
+fn build_forgejo(
+ io_mock: &Rc<RefCell<IOMock>>,
+ config: Rc<RefCell<Config>>,
+ http_downloader: Rc<RefCell<HttpDownloader>>,
+) -> Forgejo {
+ let io: Rc<RefCell<dyn IOInterface>> = io_mock.clone();
+ Forgejo::new(io, config, http_downloader)
+}
#[test]
-#[ignore = "requires getIOMock/getHttpDownloaderMock and getMockBuilder mocks of Config/JsonConfigSource with expects()/willReturn(); no mocking infrastructure exists"]
fn test_username_password_authentication_flow() {
- todo!()
+ let (io_mock, _io_guard) = get_io_mock(io_interface::NORMAL).unwrap();
+ io_mock
+ .borrow_mut()
+ .expects(
+ vec![
+ Expectation::text(MESSAGE),
+ Expectation::ask("Username: ", USERNAME),
+ Expectation::ask("Token (hidden): ", ACCESS_TOKEN),
+ ],
+ false,
+ )
+ .unwrap();
+
+ let (http_downloader, _http_guard) = get_http_downloader_mock(
+ vec![expect_full(
+ format!("https://{}/api/v1/version", ORIGIN),
+ None,
+ 200,
+ "{}",
+ vec![],
+ )],
+ true,
+ HttpDownloaderMockHandler::default(),
+ );
+
+ let config = ConfigStubBuilder::new().build_shared();
+ let (auth_source, _) = ConfigSourceMock::new("auth.json");
+ let (conf_source, conf_removed) = ConfigSourceMock::new("config.json");
+ config.borrow_mut().set_auth_config_source(auth_source);
+ config.borrow_mut().set_config_source(conf_source);
+
+ let mut forgejo = build_forgejo(&io_mock, config, http_downloader);
+
+ assert!(
+ forgejo
+ .authorize_o_auth_interactively(ORIGIN, Some(MESSAGE))
+ .unwrap()
+ .unwrap()
+ );
+ assert_eq!(
+ *conf_removed.borrow(),
+ vec![format!("forgejo-token.{}", ORIGIN)]
+ );
}
#[test]
-#[ignore = "requires getIOMock/getHttpDownloaderMock and getMockBuilder mocks of Config/JsonConfigSource with expects()/willReturn(); no mocking infrastructure exists"]
fn test_username_password_failure() {
- todo!()
+ let (io_mock, _io_guard) = get_io_mock(io_interface::NORMAL).unwrap();
+ io_mock
+ .borrow_mut()
+ .expects(
+ vec![
+ Expectation::ask("Username: ", USERNAME),
+ Expectation::ask("Token (hidden): ", ACCESS_TOKEN),
+ ],
+ false,
+ )
+ .unwrap();
+
+ let (http_downloader, _http_guard) = get_http_downloader_mock(
+ vec![expect_full(
+ format!("https://{}/api/v1/version", ORIGIN),
+ None,
+ 404,
+ "",
+ vec![],
+ )],
+ true,
+ HttpDownloaderMockHandler::default(),
+ );
+
+ let config = ConfigStubBuilder::new().build_shared();
+ let (auth_source, _) = ConfigSourceMock::new("auth.json");
+ config.borrow_mut().set_auth_config_source(auth_source);
+
+ let mut forgejo = build_forgejo(&io_mock, config, http_downloader);
+
+ assert!(
+ !forgejo
+ .authorize_o_auth_interactively(ORIGIN, None)
+ .unwrap()
+ .unwrap()
+ );
}
diff --git a/crates/shirabe/tests/util/gitlab_test.rs b/crates/shirabe/tests/util/gitlab_test.rs
index e7c8bf4..5a91415 100644
--- a/crates/shirabe/tests/util/gitlab_test.rs
+++ b/crates/shirabe/tests/util/gitlab_test.rs
@@ -1,17 +1,174 @@
//! ref: composer/tests/Composer/Test/Util/GitLabTest.php
-// Both cases construct GitLab with a mocked IO/Config/JsonConfigSource and a mocked
-// HttpDownloader to drive the username/password authentication flow. Mocking is not
-// available, and a real HttpDownloader reaches curl_multi_init (todo!()).
+use std::cell::RefCell;
+use std::rc::Rc;
+
+use shirabe::config::{Config, ConfigSourceInterface};
+use shirabe::io::IOInterface;
+use shirabe::io::io_interface;
+use shirabe::util::GitLab;
+use shirabe::util::http_downloader::{HttpDownloader, HttpDownloaderMockHandler};
+use shirabe_php_shim::PhpMixed;
+
+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};
+
+const USERNAME: &str = "username";
+const PASSWORD: &str = "password";
+const MESSAGE: &str = "mymessage";
+const ORIGIN: &str = "gitlab.com";
+const TOKEN: &str = "gitlabtoken";
+const REFRESHTOKEN: &str = "gitlabrefreshtoken";
+
+// Mirrors GitLabTest::getAuthJsonMock: a JsonConfigSource whose getName() returns
+// "auth.json" and whose addConfigSetting is a no-op (the PHP mock never stubs it).
+#[derive(Debug)]
+struct AuthJsonMock;
+
+impl ConfigSourceInterface for AuthJsonMock {
+ 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<()> {
+ Ok(())
+ }
+ fn remove_config_setting(&mut self, _name: &str) -> anyhow::Result<()> {
+ 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 {
+ "auth.json".to_string()
+ }
+}
+
+fn set_up(io_mock: &Rc<RefCell<IOMock>>, config: &Rc<RefCell<Config>>) {
+ config
+ .borrow_mut()
+ .set_auth_config_source(Box::new(AuthJsonMock));
+ let _ = io_mock;
+}
-#[ignore = "needs getIOMock/IOMock and getMockBuilder mocks for HttpDownloader/Config/JsonConfigSource (no mock infrastructure)"]
#[test]
fn test_username_password_authentication_flow() {
- todo!()
+ let (io_mock, _io_guard) = get_io_mock(io_interface::NORMAL).unwrap();
+ io_mock
+ .borrow_mut()
+ .expects(
+ vec![
+ Expectation::text(MESSAGE),
+ Expectation::ask("Username: ", USERNAME),
+ Expectation::ask("Password: ", PASSWORD),
+ ],
+ false,
+ )
+ .unwrap();
+
+ let body = format!(
+ "{{\"access_token\": \"{}\", \"refresh_token\": \"{}\", \"token_type\": \"bearer\", \"expires_in\": 7200, \"created_at\": 0}}",
+ TOKEN, REFRESHTOKEN
+ );
+ let (http_downloader, _http_guard) = get_http_downloader_mock(
+ vec![expect_full(
+ format!("http://{}/oauth/token", ORIGIN),
+ None,
+ 200,
+ body,
+ vec![],
+ )],
+ true,
+ HttpDownloaderMockHandler::default(),
+ );
+
+ let config = ConfigStubBuilder::new().build_shared();
+ set_up(&io_mock, &config);
+
+ let io: Rc<RefCell<dyn IOInterface>> = io_mock.clone();
+ let mut gitlab = GitLab::new(io, config, None, Some(http_downloader)).unwrap();
+
+ assert!(
+ gitlab
+ .authorize_oauth_interactively("http", ORIGIN, Some(MESSAGE))
+ .unwrap()
+ );
}
-#[ignore = "needs getIOMock/IOMock and getMockBuilder mocks for HttpDownloader/Config/JsonConfigSource (no mock infrastructure)"]
#[test]
fn test_username_password_failure() {
- todo!()
+ let (io_mock, _io_guard) = get_io_mock(io_interface::NORMAL).unwrap();
+ io_mock
+ .borrow_mut()
+ .expects(
+ vec![
+ Expectation::ask("Username: ", USERNAME),
+ Expectation::ask("Password: ", PASSWORD),
+ Expectation::ask("Username: ", USERNAME),
+ Expectation::ask("Password: ", PASSWORD),
+ Expectation::ask("Username: ", USERNAME),
+ Expectation::ask("Password: ", PASSWORD),
+ Expectation::ask("Username: ", USERNAME),
+ Expectation::ask("Password: ", PASSWORD),
+ Expectation::ask("Username: ", USERNAME),
+ Expectation::ask("Password: ", PASSWORD),
+ ],
+ false,
+ )
+ .unwrap();
+
+ let (http_downloader, _http_guard) = get_http_downloader_mock(
+ vec![
+ expect_full("https://gitlab.com/oauth/token", None, 401, "{}", vec![]),
+ expect_full("https://gitlab.com/oauth/token", None, 401, "{}", vec![]),
+ expect_full("https://gitlab.com/oauth/token", None, 401, "{}", vec![]),
+ expect_full("https://gitlab.com/oauth/token", None, 401, "{}", vec![]),
+ expect_full("https://gitlab.com/oauth/token", None, 401, "{}", vec![]),
+ ],
+ true,
+ HttpDownloaderMockHandler::default(),
+ );
+
+ let config = ConfigStubBuilder::new().build_shared();
+ set_up(&io_mock, &config);
+
+ let io: Rc<RefCell<dyn IOInterface>> = io_mock.clone();
+ let mut gitlab = GitLab::new(io, config, None, Some(http_downloader)).unwrap();
+
+ let err = gitlab
+ .authorize_oauth_interactively("https", ORIGIN, None)
+ .unwrap_err();
+ assert_eq!(
+ err.to_string(),
+ "Invalid GitLab credentials 5 times in a row, aborting."
+ );
}
diff --git a/crates/shirabe/tests/util/tls_helper_test.rs b/crates/shirabe/tests/util/tls_helper_test.rs
index 4717282..53e39d6 100644
--- a/crates/shirabe/tests/util/tls_helper_test.rs
+++ b/crates/shirabe/tests/util/tls_helper_test.rs
@@ -1,13 +1,138 @@
//! ref: composer/tests/Composer/Test/Util/TlsHelperTest.php
+use indexmap::IndexMap;
+use shirabe::util::tls_helper::TlsHelper;
+use shirabe_php_shim::PhpMixed;
+
+// Builds the `['subject' => ['commonName' => ..], 'extensions' => ['subjectAltName' => ..]]`
+// certificate array used by the test, given the common name and the subjectAltName string.
+fn certificate(common_name: &str, subject_alt_name: &str) -> PhpMixed {
+ let mut subject = IndexMap::new();
+ subject.insert(
+ "commonName".to_string(),
+ PhpMixed::String(common_name.to_string()),
+ );
+ let mut extensions = IndexMap::new();
+ extensions.insert(
+ "subjectAltName".to_string(),
+ PhpMixed::String(subject_alt_name.to_string()),
+ );
+ let mut cert = IndexMap::new();
+ cert.insert("subject".to_string(), PhpMixed::Array(subject));
+ cert.insert("extensions".to_string(), PhpMixed::Array(extensions));
+ PhpMixed::Array(cert)
+}
+
+/// ref: TlsHelperTest::dataCheckCertificateHost
+fn data_check_certificate_host() -> Vec<(bool, &'static str, Vec<&'static str>)> {
+ vec![
+ (true, "getcomposer.org", vec!["getcomposer.org"]),
+ (
+ true,
+ "getcomposer.org",
+ vec!["getcomposer.org", "packagist.org"],
+ ),
+ (
+ true,
+ "getcomposer.org",
+ vec!["packagist.org", "getcomposer.org"],
+ ),
+ (true, "foo.getcomposer.org", vec!["*.getcomposer.org"]),
+ (false, "xyz.foo.getcomposer.org", vec!["*.getcomposer.org"]),
+ (
+ true,
+ "foo.getcomposer.org",
+ vec!["getcomposer.org", "*.getcomposer.org"],
+ ),
+ (
+ true,
+ "foo.getcomposer.org",
+ vec!["foo.getcomposer.org", "foo*.getcomposer.org"],
+ ),
+ (
+ true,
+ "foo1.getcomposer.org",
+ vec!["foo.getcomposer.org", "foo*.getcomposer.org"],
+ ),
+ (
+ true,
+ "foo2.getcomposer.org",
+ vec!["foo.getcomposer.org", "foo*.getcomposer.org"],
+ ),
+ (
+ false,
+ "foo2.another.getcomposer.org",
+ vec!["foo.getcomposer.org", "foo*.getcomposer.org"],
+ ),
+ (
+ false,
+ "test.example.net",
+ vec!["**.example.net", "**.example.net"],
+ ),
+ (
+ false,
+ "test.example.net",
+ vec!["t*t.example.net", "t*t.example.net"],
+ ),
+ (
+ false,
+ "xyz.example.org",
+ vec!["*z.example.org", "*z.example.org"],
+ ),
+ (
+ false,
+ "foo.bar.example.com",
+ vec!["foo.*.example.com", "foo.*.example.com"],
+ ),
+ (false, "example.com", vec!["example.*", "example.*"]),
+ (true, "localhost", vec!["localhost"]),
+ (false, "localhost", vec!["*"]),
+ (false, "localhost", vec!["local*"]),
+ (false, "example.net", vec!["*.net", "*.org", "ex*.net"]),
+ (true, "example.net", vec!["*.net", "*.org", "example.net"]),
+ ]
+}
+
#[test]
-#[ignore = "TlsHelper::check_certificate_host not implemented in crates/shirabe/src"]
fn test_check_certificate_host() {
- todo!()
+ for (expected_result, hostname, mut cert_names) in data_check_certificate_host() {
+ let expected_cn = cert_names.remove(0);
+ let subject_alt_name = if cert_names.is_empty() {
+ String::new()
+ } else {
+ format!("DNS:{}", cert_names.join(",DNS:"))
+ };
+ let cert = certificate(expected_cn, &subject_alt_name);
+
+ let mut found_cn: Option<String> = None;
+ let result = TlsHelper::check_certificate_host(&cert, hostname, &mut found_cn);
+
+ if expected_result {
+ assert!(result, "hostname {hostname} should match");
+ assert_eq!(found_cn.as_deref(), Some(expected_cn));
+ } else {
+ assert!(!result, "hostname {hostname} should not match");
+ assert_eq!(found_cn, None);
+ }
+ }
}
#[test]
-#[ignore = "TlsHelper::get_certificate_names not implemented in crates/shirabe/src"]
fn test_get_certificate_names() {
- todo!()
+ let cert = certificate(
+ "example.net",
+ "DNS: example.com, IP: 127.0.0.1, DNS: getcomposer.org, Junk: blah, DNS: composer.example.org",
+ );
+
+ let names = TlsHelper::get_certificate_names(&cert).unwrap();
+
+ assert_eq!(names.cn, "example.net");
+ assert_eq!(
+ names.san,
+ vec![
+ "example.com".to_string(),
+ "getcomposer.org".to_string(),
+ "composer.example.org".to_string(),
+ ]
+ );
}