aboutsummaryrefslogtreecommitdiffhomepage
path: root/crates/shirabe/tests/command/update_command_test.rs
diff options
context:
space:
mode:
Diffstat (limited to 'crates/shirabe/tests/command/update_command_test.rs')
-rw-r--r--crates/shirabe/tests/command/update_command_test.rs624
1 files changed, 610 insertions, 14 deletions
diff --git a/crates/shirabe/tests/command/update_command_test.rs b/crates/shirabe/tests/command/update_command_test.rs
index 95b6962..6cc36bd 100644
--- a/crates/shirabe/tests/command/update_command_test.rs
+++ b/crates/shirabe/tests/command/update_command_test.rs
@@ -1,43 +1,639 @@
//! ref: composer/tests/Composer/Test/Command/UpdateCommandTest.php
+use crate::test_case::{
+ RunOptions, create_composer_lock, create_installed_json, get_application_tester, get_package,
+ init_temp_composer,
+};
+use serial_test::serial;
+use shirabe::package::Link;
+use shirabe_php_shim::PhpMixed;
+use shirabe_semver::constraint::{AnyConstraint, MatchAllConstraint};
+
+fn input(pairs: Vec<(&str, PhpMixed)>) -> Vec<(PhpMixed, PhpMixed)> {
+ pairs
+ .into_iter()
+ .map(|(k, v)| (PhpMixed::from(k), v))
+ .collect()
+}
+
+/// Minimal port of PHPUnit's `assertStringMatchesFormat` covering the placeholders these fixtures
+/// use: `%d` (integer), `%f` (float), `%s` (non-newline run), `%a` (any run incl. newlines).
+fn assert_string_matches_format(expected: &str, actual: &str) {
+ let mut pattern = String::from("(?s)^");
+ let mut chars = expected.chars().peekable();
+ while let Some(c) = chars.next() {
+ if c == '%'
+ && let Some(&next) = chars.peek()
+ {
+ match next {
+ 'd' => {
+ chars.next();
+ pattern.push_str(r"[0-9]+");
+ continue;
+ }
+ 'f' => {
+ chars.next();
+ pattern.push_str(r"[+-]?[0-9]*\.?[0-9]+([eE][+-]?[0-9]+)?");
+ continue;
+ }
+ 's' => {
+ chars.next();
+ pattern.push_str(r"[^\r\n]+");
+ continue;
+ }
+ 'a' => {
+ chars.next();
+ pattern.push_str(r".+");
+ continue;
+ }
+ _ => {}
+ }
+ }
+ pattern.push_str(&regex::escape(&c.to_string()));
+ }
+ pattern.push('$');
+ let re = regex::Regex::new(&pattern).unwrap();
+ assert!(
+ re.is_match(actual),
+ "string did not match format.\n--- expected format ---\n{expected}\n--- actual ---\n{actual}"
+ );
+}
+
+fn root_dep_and_transitive_dep() -> serde_json::Value {
+ 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", "replace": { "replaced/pkg": "1.0.0" } },
+ { "name": "dep/pkg", "version": "1.0.1", "replace": { "replaced/pkg": "1.0.1" } },
+ { "name": "dep/pkg", "version": "1.0.2", "replace": { "replaced/pkg": "1.0.2" } },
+ ] } },
+ "require": { "root/req": "1.*" },
+ })
+}
+
+/// ref: UpdateCommandTest::provideUpdates
+fn provide_updates() -> Vec<(
+ &'static str,
+ serde_json::Value,
+ Vec<(&'static str, PhpMixed)>,
+ &'static str,
+ bool,
+)> {
+ vec![
+ (
+ "simple update",
+ root_dep_and_transitive_dep(),
+ vec![],
+ "Loading composer repositories with package information
+Updating dependencies
+Lock file operations: 2 installs, 0 updates, 0 removals
+ - Locking dep/pkg (1.0.2)
+ - Locking root/req (1.0.0)
+Installing dependencies from lock file (including require-dev)
+Package operations: 2 installs, 0 updates, 0 removals
+ - Installing dep/pkg (1.0.2)
+ - Installing root/req (1.0.0)",
+ false,
+ ),
+ (
+ "simple update with very verbose output",
+ root_dep_and_transitive_dep(),
+ vec![("-vv", PhpMixed::from(true))],
+ "Loading composer repositories with package information
+Updating dependencies
+Dependency resolution completed in %f seconds
+Analyzed %d packages to resolve dependencies
+Analyzed %d rules to resolve dependencies
+Lock file operations: 2 installs, 0 updates, 0 removals
+Installs: dep/pkg:1.0.2, root/req:1.0.0
+ - Locking dep/pkg (1.0.2) from package repo (defining 4 packages)
+ - Locking root/req (1.0.0) from package repo (defining 4 packages)
+Installing dependencies from lock file (including require-dev)
+Package operations: 2 installs, 0 updates, 0 removals
+Installs: dep/pkg:1.0.2, root/req:1.0.0
+ - Installing dep/pkg (1.0.2)
+ - Installing root/req (1.0.0)",
+ false,
+ ),
+ (
+ "update with temporary constraint + --no-install",
+ root_dep_and_transitive_dep(),
+ vec![
+ ("--with", PhpMixed::List(vec![PhpMixed::from("dep/pkg:1.0.0")])),
+ ("--no-install", PhpMixed::from(true)),
+ ],
+ "Loading composer repositories with package information
+Updating dependencies
+Lock file operations: 2 installs, 0 updates, 0 removals
+ - Locking dep/pkg (1.0.0)
+ - Locking root/req (1.0.0)",
+ false,
+ ),
+ (
+ "update with temporary constraint failing resolution",
+ root_dep_and_transitive_dep(),
+ vec![("--with", PhpMixed::List(vec![PhpMixed::from("dep/pkg:^2")]))],
+ "Loading composer repositories with package information
+Updating dependencies
+Your requirements could not be resolved to an installable set of packages.
+
+ Problem 1
+ - Root composer.json requires root/req 1.* -> satisfiable by root/req[1.0.0].
+ - root/req 1.0.0 requires dep/pkg ^1 -> found dep/pkg[1.0.0, 1.0.1, 1.0.2] but it conflicts with your temporary update constraint (dep/pkg:^2).",
+ false,
+ ),
+ (
+ "update with temporary constraint failing resolution on root package",
+ root_dep_and_transitive_dep(),
+ vec![("--with", PhpMixed::List(vec![PhpMixed::from("root/req:^2")]))],
+ "The temporary constraint \"^2\" for \"root/req\" must be a subset of the constraint in your composer.json (1.*)
+Run `composer require root/req` or `composer require root/req:^2` instead to replace the constraint",
+ false,
+ ),
+ (
+ "update & bump",
+ root_dep_and_transitive_dep(),
+ vec![("--bump-after-update", PhpMixed::from(true))],
+ "Loading composer repositories with package information
+Updating dependencies
+Lock file operations: 2 installs, 0 updates, 0 removals
+ - Locking dep/pkg (1.0.2)
+ - Locking root/req (1.0.0)
+Installing dependencies from lock file (including require-dev)
+Package operations: 2 installs, 0 updates, 0 removals
+ - Installing dep/pkg (1.0.2)
+ - Installing root/req (1.0.0)
+Bumping dependencies
+<warning>Warning: Bumping dependency constraints is not recommended for libraries as it will narrow down your dependencies and may cause problems for your users.</warning>
+<warning>If your package is not a library, you can explicitly specify the \"type\" by using \"composer config type project\".</warning>
+<warning>Alternatively you can use --bump-after-update=dev to only bump dependencies within \"require-dev\".</warning>
+No requirements to update in ./composer.json.",
+ true,
+ ),
+ (
+ "update & bump with lock",
+ root_dep_and_transitive_dep(),
+ vec![
+ ("--bump-after-update", PhpMixed::from(true)),
+ ("--lock", PhpMixed::from(true)),
+ ],
+ "Loading composer repositories with package information
+Updating dependencies
+Nothing to modify in lock file
+Installing dependencies from lock file (including require-dev)
+Nothing to install, update or remove",
+ true,
+ ),
+ (
+ "update & bump dev only",
+ root_dep_and_transitive_dep(),
+ vec![("--bump-after-update", PhpMixed::from("dev"))],
+ "Loading composer repositories with package information
+Updating dependencies
+Lock file operations: 2 installs, 0 updates, 0 removals
+ - Locking dep/pkg (1.0.2)
+ - Locking root/req (1.0.0)
+Installing dependencies from lock file (including require-dev)
+Package operations: 2 installs, 0 updates, 0 removals
+ - Installing dep/pkg (1.0.2)
+ - Installing root/req (1.0.0)
+Bumping dependencies
+No requirements to update in ./composer.json.",
+ true,
+ ),
+ (
+ "update & dump with failing update",
+ root_dep_and_transitive_dep(),
+ vec![
+ ("--with", PhpMixed::List(vec![PhpMixed::from("dep/pkg:^2")])),
+ ("--bump-after-update", PhpMixed::from(true)),
+ ],
+ "Loading composer repositories with package information
+Updating dependencies
+Your requirements could not be resolved to an installable set of packages.
+
+ Problem 1
+ - Root composer.json requires root/req 1.* -> satisfiable by root/req[1.0.0].
+ - root/req 1.0.0 requires dep/pkg ^1 -> found dep/pkg[1.0.0, 1.0.1, 1.0.2] but it conflicts with your temporary update constraint (dep/pkg:^2).",
+ false,
+ ),
+ (
+ "update with replaced name filter fails to resolve",
+ root_dep_and_transitive_dep(),
+ vec![("--with", PhpMixed::List(vec![PhpMixed::from("replaced/pkg:^2")]))],
+ "Loading composer repositories with package information
+Updating dependencies
+Your requirements could not be resolved to an installable set of packages.
+
+ Problem 1
+ - Root composer.json requires root/req 1.* -> satisfiable by root/req[1.0.0].
+ - root/req 1.0.0 requires dep/pkg ^1 -> found dep/pkg[1.0.0, 1.0.1, 1.0.2] but it conflicts with your temporary update constraint (replaced/pkg:^2).",
+ false,
+ ),
+ ]
+}
+
#[test]
-#[ignore = "requires ApplicationTester and TestCase::initTempComposer/createComposerLock harness (not implemented)"]
+#[serial]
+#[ignore = "UpdateCommand::configure sets an empty InputDefinition (Phase-C: InputArgument/InputOption modeling pending), so the command rejects its own options, failing with e.g. \"The \\\"--dry-run\\\" option does not exist.\""]
fn test_update() {
- todo!()
+ for (label, composer_json, command, expected, create_lock) in provide_updates() {
+ let _tear_down = init_temp_composer(Some(&composer_json), None, None, true);
+
+ if create_lock {
+ create_composer_lock(&[], &[]);
+ }
+
+ let mut app_tester = get_application_tester();
+ let mut args = vec![
+ ("command", PhpMixed::from("update")),
+ ("--dry-run", PhpMixed::from(true)),
+ ("--no-audit", PhpMixed::from(true)),
+ ];
+ args.extend(command);
+ app_tester.run(input(args), RunOptions::default()).unwrap();
+
+ assert_string_matches_format(expected.trim(), app_tester.get_display().trim());
+ let _ = label;
+ }
}
#[test]
-#[ignore = "requires ApplicationTester and TestCase::initTempComposer/createComposerLock harness (not implemented)"]
+#[serial]
+#[ignore = "UpdateCommand::configure sets an empty InputDefinition (Phase-C: InputArgument/InputOption modeling pending), so the command rejects its own options, failing with e.g. \"The \\\"--dry-run\\\" option does not exist.\""]
fn test_update_with_patch_only() {
- todo!()
+ let composer_json = serde_json::json!({
+ "repositories": { "packages": { "type": "package", "package": [
+ { "name": "root/req", "version": "1.0.0" },
+ { "name": "root/req", "version": "1.0.1" },
+ { "name": "root/req", "version": "1.1.0" },
+ { "name": "root/req2", "version": "1.0.0" },
+ { "name": "root/req2", "version": "1.0.1" },
+ { "name": "root/req2", "version": "1.1.0" },
+ { "name": "root/req3", "version": "1.0.0" },
+ { "name": "root/req3", "version": "1.0.1" },
+ { "name": "root/req3", "version": "1.1.0" },
+ ] } },
+ "require": { "root/req": "1.*", "root/req2": "1.*", "root/req3": "1.*" },
+ });
+ let _tear_down = init_temp_composer(Some(&composer_json), None, None, true);
+
+ let package = get_package("root/req", "1.0.0");
+ let package2 = get_package("root/req2", "1.0.0");
+ let package3 = get_package("root/req3", "1.0.0");
+ create_composer_lock(&[package, package2, package3], &[]);
+
+ let mut app_tester = get_application_tester();
+ // root/req fails because of incompatible --with requirement
+ app_tester
+ .run(
+ input(vec![
+ ("command", PhpMixed::from("update")),
+ ("--dry-run", PhpMixed::from(true)),
+ ("--no-audit", PhpMixed::from(true)),
+ ("--no-install", PhpMixed::from(true)),
+ ("--patch-only", PhpMixed::from(true)),
+ (
+ "--with",
+ PhpMixed::List(vec![PhpMixed::from("root/req:^1.1")]),
+ ),
+ ]),
+ RunOptions::default(),
+ )
+ .unwrap();
+
+ assert_string_matches_format(
+ "Loading composer repositories with package information
+Updating dependencies
+Your requirements could not be resolved to an installable set of packages.
+
+ Problem 1
+ - Root composer.json requires root/req 1.*, found root/req[1.0.0, 1.0.1, 1.1.0] but it conflicts with your temporary update constraint (root/req:[[>= 1.1.0.0-dev < 2.0.0.0-dev] [>= 1.0.0.0-dev < 1.1.0.0-dev]]).",
+ app_tester.get_display().trim(),
+ );
+
+ let mut app_tester = get_application_tester();
+ // root/req upgrades to 1.0.1 as that is compatible with the --with requirement now
+ // root/req2 upgrades to 1.0.1 only due to --patch-only
+ // root/req3 does not update as it is not in the allowlist
+ app_tester
+ .run(
+ input(vec![
+ ("command", PhpMixed::from("update")),
+ ("--dry-run", PhpMixed::from(true)),
+ ("--no-audit", PhpMixed::from(true)),
+ ("--no-install", PhpMixed::from(true)),
+ ("--patch-only", PhpMixed::from(true)),
+ (
+ "--with",
+ PhpMixed::List(vec![PhpMixed::from("root/req:^1.0.1")]),
+ ),
+ (
+ "packages",
+ PhpMixed::List(vec![
+ PhpMixed::from("root/req"),
+ PhpMixed::from("root/req2"),
+ ]),
+ ),
+ ]),
+ RunOptions::default(),
+ )
+ .unwrap();
+
+ assert_string_matches_format(
+ "Loading composer repositories with package information
+Updating dependencies
+Lock file operations: 0 installs, 2 updates, 0 removals
+ - Upgrading root/req (1.0.0 => 1.0.1)
+ - Upgrading root/req2 (1.0.0 => 1.0.1)",
+ app_tester.get_display().trim(),
+ );
}
#[test]
-#[ignore = "requires ApplicationTester and TestCase::initTempComposer/createComposerLock harness (not implemented)"]
+#[serial]
+#[ignore = "UpdateCommand::configure sets an empty InputDefinition (Phase-C: InputArgument/InputOption modeling pending), so the command rejects its own options, failing with e.g. \"The \\\"--dry-run\\\" option does not exist.\""]
fn test_interactive_mode_throws_if_no_package_to_update() {
- todo!()
+ let composer_json = serde_json::json!({
+ "repositories": { "packages": { "type": "package", "package": [
+ { "name": "root/req", "version": "1.0.0" },
+ ] } },
+ "require": { "root/req": "1.*" },
+ });
+ let _tear_down = init_temp_composer(Some(&composer_json), None, None, true);
+ create_composer_lock(&[get_package("root/req", "1.0.0")], &[]);
+
+ let mut app_tester = get_application_tester();
+ app_tester.set_inputs(vec!["".to_string()]);
+ let err = app_tester
+ .run(
+ input(vec![
+ ("command", PhpMixed::from("update")),
+ ("--interactive", PhpMixed::from(true)),
+ ]),
+ RunOptions::default(),
+ )
+ .expect_err("expected an exception when there is no package to update");
+ assert!(
+ err.to_string()
+ .contains("Could not find any package with new versions available"),
+ "got: {err}"
+ );
}
#[test]
-#[ignore = "requires ApplicationTester and TestCase::initTempComposer/createComposerLock harness (not implemented)"]
+#[serial]
+#[ignore = "UpdateCommand::configure sets an empty InputDefinition (Phase-C: InputArgument/InputOption modeling pending), so the command rejects its own options, failing with e.g. \"The \\\"--dry-run\\\" option does not exist.\""]
fn test_interactive_mode_throws_if_no_package_entered() {
- todo!()
+ let composer_json = serde_json::json!({
+ "repositories": { "packages": { "type": "package", "package": [
+ { "name": "root/req", "version": "1.0.0" },
+ { "name": "root/req", "version": "1.0.1" },
+ ] } },
+ "require": { "root/req": "1.*" },
+ });
+ let _tear_down = init_temp_composer(Some(&composer_json), None, None, true);
+ create_composer_lock(&[get_package("root/req", "1.0.0")], &[]);
+
+ let mut app_tester = get_application_tester();
+ app_tester.set_inputs(vec!["".to_string()]);
+ let err = app_tester
+ .run(
+ input(vec![
+ ("command", PhpMixed::from("update")),
+ ("--interactive", PhpMixed::from(true)),
+ ]),
+ RunOptions::default(),
+ )
+ .expect_err("expected an exception when no package name is entered");
+ assert!(
+ err.to_string()
+ .contains(r#"No package named "" is installed."#),
+ "got: {err}"
+ );
+}
+
+/// ref: UpdateCommandTest::provideInteractiveUpdates
+fn provide_interactive_updates() -> Vec<(Vec<(&'static str, &'static str)>, &'static str)> {
+ vec![
+ (
+ vec![("dep/pkg", "1.0.1")],
+ "Lock file operations: 1 install, 1 update, 0 removals
+ - Locking another-dep/pkg (1.0.2)
+ - Upgrading dep/pkg (1.0.1 => 1.0.2)
+Installing dependencies from lock file (including require-dev)
+Package operations: 1 install, 1 update, 0 removals
+ - Upgrading dep/pkg (1.0.1 => 1.0.2)
+ - Installing another-dep/pkg (1.0.2)",
+ ),
+ (
+ vec![("dep/pkg", "1.0.1"), ("another-dep/pkg", "1.0.2")],
+ "Lock file operations: 0 installs, 1 update, 0 removals
+ - Upgrading dep/pkg (1.0.1 => 1.0.2)
+Installing dependencies from lock file (including require-dev)
+Package operations: 0 installs, 1 update, 0 removals
+ - Upgrading dep/pkg (1.0.1 => 1.0.2)",
+ ),
+ ]
}
#[test]
-#[ignore = "requires ApplicationTester and TestCase::initTempComposer/createComposerLock/createInstalledJson harness (not implemented)"]
+#[serial]
+#[ignore = "UpdateCommand::configure sets an empty InputDefinition (Phase-C: InputArgument/InputOption modeling pending), so the command rejects its own options, failing with e.g. \"The \\\"--dry-run\\\" option does not exist.\""]
fn test_interactive_tmp() {
- todo!()
+ for (package_names, expected) in provide_interactive_updates() {
+ let composer_json = 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" },
+ { "name": "another-dep/pkg", "version": "1.0.2" },
+ ] } },
+ "require": { "root/req": "1.*" },
+ });
+ let _tear_down = init_temp_composer(Some(&composer_json), None, None, true);
+
+ let root_package = get_package("root/req", "1.0.0");
+ let mut packages = vec![root_package.clone()];
+ for (pkg, ver) in &package_names {
+ packages.push(get_package(pkg, ver));
+ }
+
+ let mut requires = indexmap::IndexMap::new();
+ requires.insert(
+ "dep/pkg".to_string(),
+ Link::new(
+ "root/req".to_string(),
+ "dep/pkg".to_string(),
+ AnyConstraint::MatchAll(MatchAllConstraint::new(None)),
+ Some(Link::TYPE_REQUIRE.to_string()),
+ "^1".to_string(),
+ ),
+ );
+ requires.insert(
+ "another-dep/pkg".to_string(),
+ Link::new(
+ "root/req".to_string(),
+ "another-dep/pkg".to_string(),
+ AnyConstraint::MatchAll(MatchAllConstraint::new(None)),
+ Some(Link::TYPE_REQUIRE.to_string()),
+ "^1".to_string(),
+ ),
+ );
+ root_package.__set_requires(requires);
+
+ create_composer_lock(&packages, &[]);
+ create_installed_json(&packages, &[], true);
+
+ let mut app_tester = get_application_tester();
+ let mut inputs: Vec<String> = package_names
+ .iter()
+ .map(|(pkg, _)| pkg.to_string())
+ .collect();
+ inputs.push("".to_string());
+ inputs.push("yes".to_string());
+ app_tester.set_inputs(inputs);
+ app_tester
+ .run(
+ input(vec![
+ ("command", PhpMixed::from("update")),
+ ("--interactive", PhpMixed::from(true)),
+ ("--no-audit", PhpMixed::from(true)),
+ ("--dry-run", PhpMixed::from(true)),
+ ]),
+ RunOptions {
+ interactive: Some(true),
+ ..RunOptions::default()
+ },
+ )
+ .unwrap();
+
+ assert!(
+ app_tester.get_display().trim().ends_with(expected.trim()),
+ "expected display to end with:\n{expected}\n--- got ---\n{}",
+ app_tester.get_display()
+ );
+ }
}
#[test]
-#[ignore = "requires ApplicationTester and TestCase::initTempComposer harness (not implemented)"]
+#[serial]
+#[ignore = "UpdateCommand::configure sets an empty InputDefinition (Phase-C: InputArgument/InputOption modeling pending), so the command rejects its own options, failing with e.g. \"The \\\"--dry-run\\\" option does not exist.\""]
fn test_no_security_blocking_allows_insecure_packages() {
- todo!()
+ let composer_json = serde_json::json!({
+ "repositories": { "packages": {
+ "type": "package",
+ "package": [
+ { "name": "vulnerable/pkg", "version": "1.0.0" },
+ { "name": "vulnerable/pkg", "version": "1.1.0" },
+ ],
+ "security-advisories": {
+ "vulnerable/pkg": [
+ {
+ "advisoryId": "PKSA-test-001",
+ "packageName": "vulnerable/pkg",
+ "remoteId": "CVE-2024-1234",
+ "title": "Test Security Vulnerability",
+ "link": "https://example.com/advisory",
+ "cve": "CVE-2024-1234",
+ "affectedVersions": ">=1.1.0,<2.0.0",
+ "source": "test",
+ "reportedAt": "2024-01-01 00:00:00",
+ "composerRepository": "Package Repository",
+ "severity": "high",
+ "sources": [ { "name": "test", "remoteId": "CVE-2024-1234" } ],
+ },
+ ],
+ },
+ } },
+ "require": { "vulnerable/pkg": "^1.0" },
+ });
+ let _tear_down = init_temp_composer(Some(&composer_json), None, None, true);
+
+ // Test 1: Without --no-security-blocking, the vulnerable version 1.1.0 should be filtered out
+ let mut app_tester = get_application_tester();
+ app_tester
+ .run(
+ input(vec![
+ ("command", PhpMixed::from("update")),
+ ("--dry-run", PhpMixed::from(true)),
+ ("--no-audit", PhpMixed::from(true)),
+ ("--no-install", PhpMixed::from(true)),
+ ]),
+ RunOptions::default(),
+ )
+ .unwrap();
+ let display = app_tester.get_display();
+ assert!(
+ display.contains("Locking vulnerable/pkg (1.0.0)"),
+ "got: {display}"
+ );
+ assert!(
+ !display.contains("Locking vulnerable/pkg (1.1.0)"),
+ "got: {display}"
+ );
+
+ // Test 2: With --no-security-blocking, the vulnerable version 1.1.0 should be allowed
+ let mut app_tester = get_application_tester();
+ app_tester
+ .run(
+ input(vec![
+ ("command", PhpMixed::from("update")),
+ ("--dry-run", PhpMixed::from(true)),
+ ("--no-audit", PhpMixed::from(true)),
+ ("--no-install", PhpMixed::from(true)),
+ ("--no-security-blocking", PhpMixed::from(true)),
+ ]),
+ RunOptions::default(),
+ )
+ .unwrap();
+ let display = app_tester.get_display();
+ assert!(
+ display.contains("Locking vulnerable/pkg (1.1.0)"),
+ "got: {display}"
+ );
+ assert!(
+ !display.contains("Locking vulnerable/pkg (1.0.0)"),
+ "got: {display}"
+ );
}
#[test]
-#[ignore = "requires ApplicationTester and TestCase::initTempComposer harness (not implemented)"]
+#[serial]
+#[ignore = "UpdateCommand::configure sets an empty InputDefinition (Phase-C: InputArgument/InputOption modeling pending), so the command rejects its own options, failing with e.g. \"The \\\"--dry-run\\\" option does not exist.\""]
fn test_bump_after_update_without_lockfile() {
- todo!()
+ let composer_json = serde_json::json!({
+ "repositories": { "packages": { "type": "package", "package": [
+ { "name": "root/a", "version": "1.0.0" },
+ { "name": "root/a", "version": "1.1.0" },
+ ] } },
+ "require-dev": { "root/a": "^1.0.0" },
+ "config": { "lock": false },
+ });
+ let _tear_down = init_temp_composer(Some(&composer_json), None, None, true);
+
+ let mut app_tester = get_application_tester();
+ app_tester
+ .run(
+ input(vec![
+ ("command", PhpMixed::from("update")),
+ ("--dry-run", PhpMixed::from(true)),
+ ("--no-audit", PhpMixed::from(true)),
+ ("--bump-after-update", PhpMixed::from("dev")),
+ ]),
+ RunOptions::default(),
+ )
+ .unwrap();
+
+ assert_string_matches_format(
+ "Loading composer repositories with package information
+Updating dependencies
+Package operations: 1 install, 0 updates, 0 removals
+ - Installing root/a (1.1.0)
+Bumping dependencies
+./composer.json would be updated with:
+ - require-dev.root/a: ^1.1.0",
+ app_tester.get_display().trim(),
+ );
}