From 266d6f6d876ab3eb020b88ef6caee0f91776db37 Mon Sep 17 00:00:00 2001 From: akbash Date: Wed, 16 Sep 2026 23:22:37 +0300 Subject: fix: respect opencode tui.json registrations (#4250) refs #4240 Co-authored-by: akbash-bot <300245827+akbash-bot@users.noreply.github.com> Co-authored-by: JJ Liebig --- src/integration/tests.rs | 118 +++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 115 insertions(+), 3 deletions(-) (limited to 'src/integration/tests.rs') diff --git a/src/integration/tests.rs b/src/integration/tests.rs index 1a102992..f7e2154e 100644 --- a/src/integration/tests.rs +++ b/src/integration/tests.rs @@ -2326,6 +2326,69 @@ fn install_opencode_writes_server_and_tui_plugins() { let _ = fs::remove_dir_all(base); } +#[cfg(unix)] +#[test] +fn opencode_reuses_json_registration_in_symlinked_config_directory() { + let _lock = integration_env_lock(); + let base = unique_base(); + let home = base.join("home"); + let dotfiles = base.join("dotfiles"); + let dir = home.join(".config/opencode"); + fs::create_dir_all(home.join(".config")).unwrap(); + fs::create_dir_all(&dotfiles).unwrap(); + std::os::unix::fs::symlink(&dotfiles, &dir).unwrap(); + std::env::set_var("HOME", &home); + let json_path = dir.join("tui.json"); + let original = "{\n // User preferences\n \"theme\":\"system\",\n \"plugin\":[\"other\",[\"./herdr-tui-session.js\",{\"enabled\":true}]]\n}\n"; + fs::write(&json_path, original).unwrap(); + + for _ in 0..2 { + let installed = install_opencode().unwrap(); + assert_eq!(installed.tui_config_path, json_path); + assert!(installed.cli_config_path.is_none()); + assert!(!dir.join("tui.jsonc").exists()); + assert_eq!(fs::read_to_string(&json_path).unwrap(), original); + assert_eq!( + integration_status_at( + crate::api::schema::IntegrationTarget::Opencode, + installed.plugin_path, + OPENCODE_INTEGRATION_VERSION, + ) + .state, + IntegrationStatusKind::Current + ); + } + + // Older installs may have registered the same plugin in both files. + let jsonc_path = dir.join("tui.jsonc"); + fs::write( + &jsonc_path, + r#"{"plugin":["./herdr-tui-session.js","another"]}"#, + ) + .unwrap(); + assert_eq!(install_opencode().unwrap().tui_config_path, jsonc_path); + let result = uninstall_opencode().unwrap(); + assert_eq!( + result.updated_tui_configs, + vec![jsonc_path.clone(), json_path.clone()] + ); + let json = fs::read_to_string(&json_path).unwrap(); + assert!(json.contains("// User preferences")); + assert!(!json.contains("herdr-tui-session.js")); + assert!(json.contains("\"other\"")); + assert!(json.contains("\"system\"")); + assert_eq!( + serde_json::from_str::(&fs::read_to_string(jsonc_path).unwrap()).unwrap(), + json!({"plugin":["another"]}) + ); + assert!(uninstall_opencode().unwrap().updated_tui_configs.is_empty()); + assert_eq!(fs::read_link(&dir).unwrap(), dotfiles); + assert!(!result.plugin_path.exists()); + assert!(!result.tui_plugin_path.exists()); + std::env::remove_var("HOME"); + fs::remove_dir_all(base).unwrap(); +} + #[test] fn opencode_install_defers_v2_registration_while_migration_pending() { let _lock = integration_env_lock(); @@ -2427,6 +2490,42 @@ fn opencode_hard_link_rejection_precedes_install_and_uninstall_asset_changes() { fs::remove_dir_all(base).unwrap(); } +#[test] +fn opencode_json_config_validation_precedes_asset_changes() { + let _lock = integration_env_lock(); + let base = unique_base(); + let home = base.join("home"); + let dir = home.join(".config/opencode"); + fs::create_dir_all(dir.join("plugins")).unwrap(); + std::env::set_var("HOME", &home); + let plugin = dir.join("plugins").join(OPENCODE_PLUGIN_INSTALL_NAME); + fs::write(&plugin, "previous integration").unwrap(); + let config = dir.join("tui.json"); + fs::write(&config, r#"{"plugin":{}}"#).unwrap(); + assert!(install_opencode() + .unwrap_err() + .to_string() + .contains("plugin list")); + assert_eq!(fs::read_to_string(&plugin).unwrap(), "previous integration"); + let original = r#"{"plugin":["./herdr-tui-session.js"]}"#; + fs::write(&config, original).unwrap(); + let alias = base.join("linked-config"); + fs::hard_link(&config, &alias).unwrap(); + for error in [ + install_opencode().unwrap_err(), + uninstall_opencode().unwrap_err(), + ] { + assert!(error.to_string().contains("multiple hard links")); + assert!(error.to_string().contains("tui.json")); + } + assert_eq!(fs::read_to_string(&plugin).unwrap(), "previous integration"); + assert_eq!(fs::read_to_string(&alias).unwrap(), original); + assert!(!dir.join("tui.jsonc").exists()); + assert!(!dir.join(OPENCODE_TUI_PLUGIN_INSTALL_NAME).exists()); + std::env::remove_var("HOME"); + fs::remove_dir_all(base).unwrap(); +} + #[cfg(windows)] #[test] fn opencode_recovery_copy_blocks_retry_before_parsing_or_asset_changes() { @@ -2539,12 +2638,15 @@ fn uninstall_opencode_removes_plugins_and_managed_tui_config_entry() { assert!(result.removed_plugin); assert!(result.removed_tui_plugin); - assert!(result.updated_tui_config); + assert_eq!( + result.updated_tui_configs, + vec![installed.tui_config_path.clone()] + ); assert!(!result.plugin_path.exists()); assert!(!result.tui_plugin_path.exists()); - assert!(result.tui_config_path.exists()); + assert!(installed.tui_config_path.exists()); let tui_config: Value = - serde_json::from_str(&fs::read_to_string(&result.tui_config_path).unwrap()).unwrap(); + serde_json::from_str(&fs::read_to_string(&installed.tui_config_path).unwrap()).unwrap(); assert_eq!(tui_config, json!({})); assert_eq!(installed.plugin_path, result.plugin_path); @@ -2588,6 +2690,12 @@ fn uninstall_opencode_removes_plugins_when_tui_config_is_invalid() { fs::write(&plugin_path, OPENCODE_PLUGIN_ASSET).unwrap(); fs::write(&tui_plugin_path, OPENCODE_TUI_PLUGIN_ASSET).unwrap(); fs::write(opencode_dir.join("tui.jsonc"), "{\"plugin\":").unwrap(); + let json_path = opencode_dir.join("tui.json"); + fs::write( + &json_path, + r#"{"plugin":["./herdr-tui-session.js","other"]}"#, + ) + .unwrap(); std::env::set_var("HOME", &home); let err = uninstall_opencode().unwrap_err().to_string(); @@ -2595,6 +2703,10 @@ fn uninstall_opencode_removes_plugins_when_tui_config_is_invalid() { assert!(err.contains("failed to parse OpenCode TUI config")); assert!(!plugin_path.exists()); assert!(!tui_plugin_path.exists()); + assert_eq!( + serde_json::from_str::(&fs::read_to_string(json_path).unwrap()).unwrap(), + json!({"plugin":["other"]}) + ); std::env::remove_var("HOME"); let _ = fs::remove_dir_all(base); -- cgit v1.2.3