Handle new profile form in migrate_settings and migrate_language_setting

Joseph T. Lyons created

Change summary

crates/migrator/src/migrations.rs | 36 +++++++++---
crates/migrator/src/migrator.rs   | 92 +++++++++++++++++++++++++++++++++
2 files changed, 119 insertions(+), 9 deletions(-)

Detailed changes

crates/migrator/src/migrations.rs 🔗

@@ -29,9 +29,16 @@ pub(crate) fn migrate_settings(
 
     if let Some(profiles) = root_object.get_mut("profiles") {
         if let Some(profiles_object) = profiles.as_object_mut() {
-            for (_profile_name, profile_settings) in profiles_object.iter_mut() {
-                if let Some(profile_map) = profile_settings.as_object_mut() {
-                    migrate_one(profile_map)?;
+            for profile_value in profiles_object.values_mut() {
+                if let Some(profile_map) = profile_value.as_object_mut() {
+                    if let Some(inner) = profile_map
+                        .get_mut("settings")
+                        .and_then(|v| v.as_object_mut())
+                    {
+                        migrate_one(inner)?;
+                    } else {
+                        migrate_one(profile_map)?;
+                    }
                 }
             }
         }
@@ -93,12 +100,23 @@ pub(crate) fn migrate_language_setting(
         if let Some(profiles_object) = profiles.as_object_mut() {
             let profile_names: Vec<String> = profiles_object.keys().cloned().collect();
             for profile_name in &profile_names {
-                if let Some(profile_settings) = profiles_object.get_mut(profile_name.as_str()) {
-                    apply_to_value_and_languages(
-                        profile_settings,
-                        &["profiles", profile_name],
-                        migrate_fn,
-                    )?;
+                if let Some(profile_value) = profiles_object.get_mut(profile_name.as_str()) {
+                    if let Some(settings_value) = profile_value
+                        .as_object_mut()
+                        .and_then(|m| m.get_mut("settings"))
+                    {
+                        apply_to_value_and_languages(
+                            settings_value,
+                            &["profiles", profile_name],
+                            migrate_fn,
+                        )?;
+                    } else {
+                        apply_to_value_and_languages(
+                            profile_value,
+                            &["profiles", profile_name],
+                            migrate_fn,
+                        )?;
+                    }
                 }
             }
         }

crates/migrator/src/migrator.rs 🔗

@@ -4930,6 +4930,98 @@ mod tests {
         );
     }
 
+    #[test]
+    fn test_migration_helpers_handle_various_profile_forms() {
+        let setting = "a_setting";
+        let old_value = "old_value";
+        let new_value = "new_value";
+
+        fn language_setting_fn(value: &mut serde_json::Value, _: &[&str]) -> anyhow::Result<()> {
+            if let Some(obj) = value.as_object_mut() {
+                if let Some(v) = obj.get_mut("a_setting") {
+                    *v = serde_json::json!("new_value");
+                }
+            }
+            Ok(())
+        }
+
+        let mut settings_fn = |map: &mut serde_json::Map<String, serde_json::Value>| {
+            if let Some(v) = map.get_mut(setting) {
+                *v = serde_json::json!(new_value);
+            }
+            Ok(())
+        };
+
+        // Legacy form
+        let input = serde_json::json!({
+            "profiles": {
+                "work": {
+                    setting: old_value
+                }
+            }
+        });
+        let expected = serde_json::json!({
+            "profiles": {
+                "work": {
+                    setting: new_value
+                }
+            }
+        });
+
+        let mut value = input.clone();
+        migrations::migrate_settings(&mut value, &mut settings_fn).unwrap();
+        assert_eq!(value, expected);
+
+        let mut value = input;
+        migrations::migrate_language_setting(&mut value, language_setting_fn).unwrap();
+        assert_eq!(value, expected);
+
+        // Form after migration: `m_2026_04_01`
+        let input = serde_json::json!({
+            "profiles": {
+                "work": {
+                    "settings": {
+                        setting: old_value
+                    }
+                }
+            }
+        });
+        let expected = serde_json::json!({
+            "profiles": {
+                "work": {
+                    "settings": {
+                        setting: new_value
+                    }
+                }
+            }
+        });
+
+        let mut value = input.clone();
+        migrations::migrate_settings(&mut value, &mut settings_fn).unwrap();
+        assert_eq!(value, expected);
+
+        let mut value = input;
+        migrations::migrate_language_setting(&mut value, language_setting_fn).unwrap();
+        assert_eq!(value, expected);
+
+        // Base-only form after migration: `m_2026_04_01` (no settings to migrate)
+        let input = serde_json::json!({
+            "profiles": {
+                "work": {
+                    "base": "default"
+                }
+            }
+        });
+
+        let mut value = input.clone();
+        migrations::migrate_settings(&mut value, &mut settings_fn).unwrap();
+        assert_eq!(value, input);
+
+        let mut value = input.clone();
+        migrations::migrate_language_setting(&mut value, language_setting_fn).unwrap();
+        assert_eq!(value, input);
+    }
+
     #[test]
     fn test_rename_web_search_to_search_web_root_level_profile() {
         assert_migrate_with_migrations(