diff --git a/crates/ely_browser_core/src/state/sync_profiles.rs b/crates/ely_browser_core/src/state/sync_profiles.rs index a7973b6..3c4ace1 100644 --- a/crates/ely_browser_core/src/state/sync_profiles.rs +++ b/crates/ely_browser_core/src/state/sync_profiles.rs @@ -24,18 +24,28 @@ impl BrowserCore { summary.record_skipped(); return Ok(()); } + let name = record.name.trim().to_string(); - let existing_index = - self.profiles.iter().position(|profile| profile.id() == &profile_id).or_else(|| { - self.profiles - .iter() - .position(|profile| profile.name().eq_ignore_ascii_case(record.name.trim())) + let existing_index = self + .profiles + .iter() + .position(|profile| profile.id() == &profile_id && profile.kind() == &kind) + .or_else(|| { + self.profiles.iter().position(|profile| { + profile.kind() == &kind && profile.name().eq_ignore_ascii_case(&name) + }) }); let id = existing_index .and_then(|index| self.profiles.get(index).map(|profile| profile.id().clone())) - .unwrap_or_else(|| profile_id.clone()); + .unwrap_or_else(|| { + if self.profiles.iter().any(|profile| profile.id() == &profile_id) { + ProfileId::new() + } else { + profile_id.clone() + } + }); context.register_profile_alias(profile_id, id.clone()); - let mut profile = Profile::new(record.name, record.color_hex, kind); + let mut profile = Profile::new(name, record.color_hex, kind); profile.set_sync_policy(record.sync_policy.into()); let profile = Profile::restore(id, profile); diff --git a/crates/ely_browser_core/tests/sync_profiles.rs b/crates/ely_browser_core/tests/sync_profiles.rs index 14f86bd..591e328 100644 --- a/crates/ely_browser_core/tests/sync_profiles.rs +++ b/crates/ely_browser_core/tests/sync_profiles.rs @@ -112,3 +112,68 @@ fn paused_profile_data_is_omitted_from_sync_snapshots() -> Result<(), Box Result<(), Box> { + let mut source_config = InitialBrowserConfig::ely_defaults()?; + source_config.profile_name = "Private".to_string(); + let mut source = BrowserCore::new(source_config)?; + source.navigate_active_tab(UrlText::parse("https://example.com/remote")?)?; + let bytes = source.build_sync_snapshot_bytes()?; + + let mut target = BrowserCore::new(InitialBrowserConfig::private_window()?)?; + let private_profile_id = target.snapshot()?.active_profile_id; + target.navigate_active_tab(UrlText::parse("https://private.example/secret")?)?; + target.apply_sync_snapshot_bytes(&bytes)?; + let snapshot = target.snapshot()?; + + assert!(snapshot.profiles.iter().any(|profile| { + profile.id() == &private_profile_id && profile.kind() == &ProfileKind::Private + })); + assert!(snapshot.profiles.iter().any(|profile| { + profile.id() != &private_profile_id + && profile.name() == "Private" + && profile.kind() == &ProfileKind::Standard + })); + let outbound = String::from_utf8(target.build_sync_snapshot_bytes()?)?; + assert!(!outbound.contains("https://private.example/secret")); + assert!(outbound.contains("https://example.com/remote")); + Ok(()) +} + +#[test] +fn standard_profile_sync_remaps_a_private_profile_id_collision() -> Result<(), Box> { + let mut target = BrowserCore::new(InitialBrowserConfig::private_window()?)?; + let private_profile_id = target.snapshot()?.active_profile_id; + target.navigate_active_tab(UrlText::parse("https://private.example/id-secret")?)?; + + let mut source_config = InitialBrowserConfig::ely_defaults()?; + source_config.profile_id = Some(private_profile_id.clone()); + source_config.profile_name = " Synced ".to_string(); + let mut source = BrowserCore::new(source_config)?; + source.navigate_active_tab(UrlText::parse("https://example.com/id-remote")?)?; + let bytes = source.build_sync_snapshot_bytes()?; + target.apply_sync_snapshot_bytes(&bytes)?; + target.apply_sync_snapshot_bytes(&bytes)?; + let snapshot = target.snapshot()?; + + assert!(snapshot.profiles.iter().any(|profile| { + profile.id() == &private_profile_id && profile.kind() == &ProfileKind::Private + })); + assert_eq!( + snapshot + .profiles + .iter() + .filter(|profile| { + profile.id() != &private_profile_id + && profile.name() == "Synced" + && profile.kind() == &ProfileKind::Standard + }) + .count(), + 1 + ); + let outbound = String::from_utf8(target.build_sync_snapshot_bytes()?)?; + assert!(!outbound.contains("https://private.example/id-secret")); + assert!(outbound.contains("https://example.com/id-remote")); + Ok(()) +}