Skip to main content

keepass_ng/db/
merge.rs

1use crate::db::*;
2use chrono::NaiveDateTime;
3use std::collections::{HashMap, VecDeque};
4use uuid::Uuid;
5
6#[derive(Debug, Clone)]
7pub enum MergeEventType {
8    EntryCreated,
9    EntryDeleted,
10    EntryLocationUpdated,
11    EntryUpdated,
12
13    GroupCreated,
14    GroupDeleted,
15    GroupLocationUpdated,
16    GroupUpdated,
17
18    IconCreated,
19    IconUpdated,
20}
21
22#[derive(Debug, Clone)]
23pub struct MergeEvent {
24    /// The uuid of the node (entry or group) affected by
25    /// the merge event.
26    pub node_uuid: Uuid,
27
28    pub event_type: MergeEventType,
29}
30
31#[derive(Debug, Default, Clone)]
32pub struct MergeLog {
33    pub warnings: Vec<String>,
34    pub events: Vec<MergeEvent>,
35}
36
37/// Errors while merge two databases
38#[derive(thiserror::Error, Debug)]
39pub enum MergeError {
40    #[error("{0}")]
41    GenericError(String),
42
43    #[error("Could not find group at {0:?}")]
44    FindGroupError(Vec<Uuid>),
45
46    #[error("Could not find entry at {0:?}")]
47    FindEntryError(Vec<Uuid>),
48
49    #[error("Entries with UUID {0} have the same modification time but have diverged.")]
50    EntryModificationTimeNotUpdated(String),
51
52    #[error("Groups with UUID {0} have the same modification time but have diverged.")]
53    GroupModificationTimeNotUpdated(String),
54
55    #[error("Found history entries with the same timestamp ({0}) for entry {1}.")]
56    DuplicateHistoryEntries(String, String),
57}
58
59impl MergeLog {
60    pub fn merge_with(&self, other: &MergeLog) -> MergeLog {
61        let mut response = MergeLog::default();
62        response.warnings.append(self.warnings.clone().as_mut());
63        response.warnings.append(other.warnings.clone().as_mut());
64        response.events.append(self.events.clone().as_mut());
65        response.events.append(other.events.clone().as_mut());
66        response
67    }
68
69    pub fn append(&mut self, other: &MergeLog) {
70        self.warnings.append(other.warnings.clone().as_mut());
71        self.events.append(other.events.clone().as_mut());
72    }
73}
74
75impl Database {
76    /// Merge this database with another version of this same database.
77    /// This function will use the UUIDs to detect that entries and groups are
78    /// the same.
79    pub fn merge(&mut self, other: &Database) -> Result<MergeLog, MergeError> {
80        let mut log = MergeLog::default();
81        log.append(&self.merge_group(&[], &other.root, false)?);
82        log.append(&self.merge_icons(other)?);
83        log.append(&self.merge_deletions(other)?);
84        Ok(log)
85    }
86
87    fn merge_icons(&mut self, other: &Database) -> Result<MergeLog, MergeError> {
88        let mut log = MergeLog::default();
89
90        for (uuid, source_icon) in &other.meta.custom_icons {
91            let Some(destination_icon) = self.meta.custom_icons.get_mut(uuid) else {
92                if let Some(Some(deletion_time)) = self.deleted_objects.get(uuid)
93                    && source_icon.last_modification_time.is_none_or(|modified| modified <= *deletion_time)
94                {
95                    continue;
96                }
97
98                self.meta.custom_icons.insert(*uuid, source_icon.clone());
99                log.events.push(MergeEvent {
100                    node_uuid: *uuid,
101                    event_type: MergeEventType::IconCreated,
102                });
103                continue;
104            };
105
106            let source_is_newer = match (source_icon.last_modification_time, destination_icon.last_modification_time) {
107                (Some(source), Some(destination)) => source > destination,
108                (Some(_), None) => true,
109                _ => false,
110            };
111            if source_is_newer {
112                *destination_icon = source_icon.clone();
113                log.events.push(MergeEvent {
114                    node_uuid: *uuid,
115                    event_type: MergeEventType::IconUpdated,
116                });
117            }
118        }
119
120        Ok(log)
121    }
122
123    fn merge_deletions(&mut self, other: &Database) -> Result<MergeLog, MergeError> {
124        // Utility function to search for a UUID in the deletion queue.
125        let is_in_deleted_queue = |uuid: Uuid, deleted_groups_queue: &VecDeque<(Uuid, NaiveDateTime)>| -> bool {
126            for (deleted_uuid, _) in deleted_groups_queue {
127                // This group still has a child group, but it is not going to be deleted.
128                if *deleted_uuid == uuid {
129                    return true;
130                }
131            }
132            false
133        };
134        let mut log = MergeLog::default();
135        let mut new_deleted_objects = self.deleted_objects.clone();
136        // We start by deleting the entries, since we will only remove groups if they are empty.
137        for (uuid, deletion_time) in &other.deleted_objects {
138            let deletion_time = deletion_time.unwrap_or_else(Times::now);
139            if new_deleted_objects.contains_key(uuid) {
140                continue;
141            }
142            let entry_location = match Self::find_node_location(&self.root, *uuid) {
143                Some(l) => l,
144                None => continue,
145            };
146            let parent_group = Group::find_group(&self.root, &entry_location).ok_or(MergeError::FindGroupError(entry_location))?;
147
148            let entry = match Group::find_entry(&parent_group, &[*uuid]) {
149                Some(e) => e,
150                // This uuid might refer to a group, which will be handled later.
151                None => continue,
152            };
153
154            let entry_last_modification = match with_node::<Entry, _, _>(&entry, |e| e.get_times().get_last_modification()).unwrap() {
155                Some(t) => t,
156                None => {
157                    log.warnings.push(format!(
158                        "Entry {} did not have a last modification timestamp",
159                        entry.borrow().downcast_ref::<Entry>().unwrap().uuid
160                    ));
161                    Times::now()
162                }
163            };
164            if entry_last_modification < deletion_time {
165                with_node_mut::<Group, _, _>(&parent_group, |pg| pg.remove_node(*uuid)).unwrap()?;
166                log.events.push(MergeEvent {
167                    event_type: MergeEventType::EntryDeleted,
168                    node_uuid: *uuid,
169                });
170                new_deleted_objects.insert(*uuid, Some(deletion_time));
171            }
172        }
173        let mut deleted_groups_queue: VecDeque<(Uuid, NaiveDateTime)> = VecDeque::new();
174        for (uuid, deletion_time) in &other.deleted_objects {
175            if new_deleted_objects.contains_key(uuid) {
176                continue;
177            }
178            deleted_groups_queue.push_back((*uuid, deletion_time.unwrap_or_else(Times::now)));
179        }
180        while !deleted_groups_queue.is_empty() {
181            let (deleted_uuid, deletion_time) = deleted_groups_queue.pop_front().unwrap();
182            if new_deleted_objects.contains_key(&deleted_uuid) {
183                continue;
184            }
185            let group_location = match Self::find_node_location(&self.root, deleted_uuid) {
186                Some(l) => l,
187                None => continue,
188            };
189            let parent_group = Group::find_group(&self.root, &group_location).ok_or(MergeError::FindGroupError(group_location))?;
190
191            let group = match Group::find_group(&parent_group, &[deleted_uuid]) {
192                Some(g) => g,
193                None => {
194                    // The node might be an entry, since we didn't necessarily removed all the
195                    // entries that were in the deleted objects of the source database.
196                    continue;
197                }
198            };
199            // Not deleting a group if it still has entries.
200            if !with_node::<Group, _, _>(&group, |g| g.entries()).unwrap().is_empty() {
201                continue;
202            }
203            // This group still has a child group that might get deleted in the future, so we delay
204            // decision to delete it or not.
205            if with_node::<Group, _, _>(&group, |g| {
206                g.groups()
207                    .iter()
208                    .any(|child| is_in_deleted_queue(child.borrow().get_uuid(), &deleted_groups_queue))
209            })
210            .unwrap()
211            {
212                deleted_groups_queue.push_back((deleted_uuid, deletion_time));
213                continue;
214            }
215            // This group still a groups that won't be deleted, so we don't delete it.
216            if !with_node::<Group, _, _>(&group, |g| g.groups()).unwrap().is_empty() {
217                continue;
218            }
219            let group_last_modification = match with_node::<Group, _, _>(&group, |g| g.get_times().get_last_modification()).unwrap() {
220                Some(t) => t,
221                None => {
222                    log.warnings.push(format!(
223                        "Group {} did not have a last modification timestamp",
224                        group.borrow().downcast_ref::<Group>().unwrap().uuid
225                    ));
226                    Times::now()
227                }
228            };
229            if group_last_modification < deletion_time {
230                with_node_mut::<Group, _, _>(&parent_group, |pg| pg.remove_node(deleted_uuid)).unwrap()?;
231                log.events.push(MergeEvent {
232                    event_type: MergeEventType::GroupDeleted,
233                    node_uuid: deleted_uuid,
234                });
235                new_deleted_objects.insert(deleted_uuid, Some(deletion_time));
236            }
237        }
238        self.deleted_objects = new_deleted_objects;
239        Ok(log)
240    }
241
242    pub(crate) fn find_node_location(root: &NodePtr, id: Uuid) -> Option<Vec<Uuid>> {
243        // let root_uuid = root.borrow().get_uuid();
244        // let mut current_location = vec![root_uuid];
245        for node in &group_get_children(root).unwrap_or_default() {
246            let node_uuid = node.borrow().get_uuid();
247            if node_is_entry(node) {
248                if node_uuid == id {
249                    // current_location.push(node_uuid);
250                    // return Some(current_location);
251                    return Some(vec![]);
252                }
253            } else if node_is_group(node) {
254                if node_uuid == id {
255                    // current_location.push(node_uuid);
256                    // return Some(current_location);
257                    return Some(vec![]);
258                }
259                #[allow(unused_mut)]
260                if let Some(mut location) = Group::find_node_location(node, id) {
261                    // current_location.append(&mut location);
262                    // return Some(current_location);
263                    return Some(location);
264                }
265            }
266        }
267        None
268    }
269
270    fn merge_group(&self, current_group_path: &[Uuid], current_group: &NodePtr, is_in_deleted_group: bool) -> Result<MergeLog, MergeError> {
271        let mut log = MergeLog::default();
272        if let Some(destination_group_location) = Self::find_node_location(&self.root, current_group.borrow().get_uuid()) {
273            let mut destination_group_path = destination_group_location.clone();
274            destination_group_path.push(current_group.borrow().get_uuid());
275            let destination_group =
276                Group::find_group(&self.root, &destination_group_path).ok_or(MergeError::FindGroupError(destination_group_path))?;
277            let group_update_merge_events = Group::merge_with(&destination_group, current_group)?;
278            log.append(&group_update_merge_events);
279        }
280        for other_entry in &with_node::<Group, _, _>(current_group, |g| g.entries()).unwrap() {
281            let other_entry_uuid = other_entry.borrow().get_uuid();
282            // find the existing location
283            let destination_entry_location = Self::find_node_location(&self.root, other_entry_uuid);
284            // The group already exists in the destination database.
285            if let Some(destination_entry_location) = destination_entry_location {
286                let mut existing_entry_location = destination_entry_location.clone();
287                existing_entry_location.push(other_entry_uuid);
288                // The entry already exists but is not at the right location. We might have to
289                // relocate it.
290                let existing_entry = Group::find_entry(&self.root, &existing_entry_location)
291                    .ok_or(MergeError::FindEntryError(existing_entry_location.clone()))?
292                    .borrow()
293                    .duplicate();
294                // The entry already exists but is not at the right location. We might have to
295                // relocate it.
296                if current_group_path.last() != destination_entry_location.last() && !is_in_deleted_group {
297                    let source_location_changed_time =
298                        match with_node::<Entry, _, _>(other_entry, |e| e.get_times().get_location_changed()).unwrap() {
299                            Some(t) => t,
300                            None => {
301                                log.warnings
302                                    .push(format!("Entry {other_entry_uuid} did not have a location updated timestamp"));
303                                Times::epoch()
304                            }
305                        };
306                    let destination_location_changed =
307                        match with_node::<Entry, _, _>(&existing_entry, |e| e.get_times().get_location_changed()).unwrap() {
308                            Some(t) => t,
309                            None => {
310                                log.warnings
311                                    .push(format!("Entry {other_entry_uuid} did not have a location updated timestamp"));
312                                Times::now()
313                            }
314                        };
315                    if source_location_changed_time > destination_location_changed {
316                        log.events.push(MergeEvent {
317                            event_type: MergeEventType::EntryLocationUpdated,
318                            node_uuid: other_entry_uuid,
319                        });
320                        self.relocate_node(
321                            other_entry_uuid,
322                            &destination_entry_location,
323                            current_group_path,
324                            source_location_changed_time,
325                        )?;
326                        // Update the location of the current entry in case we have to update it
327                        // after.
328                        existing_entry_location = current_group_path.to_owned();
329                        existing_entry_location.push(other_entry_uuid);
330                        with_node_mut::<Entry, _, _>(&existing_entry, |e| {
331                            e.get_times_mut().set_location_changed(Some(source_location_changed_time));
332                        });
333                    }
334                }
335                if !has_diverged_from(&existing_entry, other_entry) {
336                    continue;
337                }
338                // The entry already exists and is at the right location, so we can proceed and merge
339                // the two entries.
340                let (merged_entry, entry_merge_log) = Entry::merge(&existing_entry, other_entry)?;
341                let merged_entry = match merged_entry {
342                    Some(m) => m,
343                    None => continue,
344                };
345                if node_is_equals_to(&existing_entry, &merged_entry) {
346                    continue;
347                }
348                let existing_entry =
349                    Group::find_entry(&self.root, &existing_entry_location).ok_or(MergeError::FindEntryError(existing_entry_location))?;
350                // *existing_entry = merged_entry.clone();
351                with_node_mut::<Entry, _, _>(&existing_entry, |e| e.replaced_with(&merged_entry)).unwrap();
352                log.events.push(MergeEvent {
353                    event_type: MergeEventType::EntryUpdated,
354                    node_uuid: merged_entry.borrow().get_uuid(),
355                });
356                log.append(&entry_merge_log);
357                continue;
358            }
359            if self.deleted_objects.contains_key(&other_entry_uuid) {
360                continue;
361            }
362            // We don't create new entries that exist under a deleted group.
363            if is_in_deleted_group {
364                continue;
365            }
366            // The entry doesn't exist in the destination, we create it
367            // let new_entry = other_entry.to_owned().clone();
368            let new_entry = other_entry.borrow().duplicate();
369            let new_entry_parent_group =
370                Group::find_group(&self.root, current_group_path).ok_or(MergeError::FindGroupError(current_group_path.to_owned()))?;
371
372            // new_entry_parent_group.add_child(new_entry.clone());
373            group_add_child(&new_entry_parent_group, new_entry.clone(), 0).unwrap();
374            // TODO should we update the time info for the entry?
375            log.events.push(MergeEvent {
376                event_type: MergeEventType::EntryCreated,
377                node_uuid: new_entry.borrow().get_uuid(),
378            });
379        }
380        for other_group in &current_group.borrow().downcast_ref::<Group>().unwrap().groups() {
381            let mut new_group_location = current_group_path.to_owned();
382            let other_group_uuid = other_group.borrow().get_uuid();
383            new_group_location.push(other_group_uuid);
384            if self.deleted_objects.contains_key(&other_group_uuid) || is_in_deleted_group {
385                let new_merge_log = self.merge_group(&new_group_location, other_group, true)?;
386                log.append(&new_merge_log);
387                continue;
388            }
389            let destination_group_location = Self::find_node_location(&self.root, other_group_uuid);
390            // The group already exists in the destination database.
391            if let Some(destination_group_location) = &destination_group_location {
392                if current_group_path != destination_group_location {
393                    let mut existing_group_location = destination_group_location.clone();
394                    existing_group_location.push(other_group_uuid);
395                    // The group already exists but is not at the right location. We might have to
396                    // relocate it.
397                    let existing_group = Group::find_group(&self.root, &existing_group_location)
398                        .ok_or(MergeError::FindGroupError(existing_group_location))?;
399                    let existing_group_location_changed =
400                        match with_node::<Group, _, _>(&existing_group, |g| g.get_times().get_location_changed()).unwrap() {
401                            Some(t) => t,
402                            None => {
403                                let uuid = existing_group.borrow().get_uuid();
404                                log.warnings.push(format!("Entry {uuid} did not have a location changed timestamp"));
405                                Times::now()
406                            }
407                        };
408                    let other_group_location_changed =
409                        match with_node::<Group, _, _>(other_group, |g| g.get_times().get_location_changed()).unwrap() {
410                            Some(t) => t,
411                            None => {
412                                log.warnings
413                                    .push(format!("Entry {other_group_uuid} did not have a location changed timestamp"));
414                                Times::epoch()
415                            }
416                        };
417                    // The other group was moved after the current group, so we have to relocate it.
418                    if existing_group_location_changed < other_group_location_changed {
419                        self.relocate_node(
420                            other_group_uuid,
421                            destination_group_location,
422                            current_group_path,
423                            other_group_location_changed,
424                        )?;
425                        log.events.push(MergeEvent {
426                            event_type: MergeEventType::GroupLocationUpdated,
427                            node_uuid: other_group_uuid,
428                        });
429                        let new_merge_log = self.merge_group(&new_group_location, other_group, is_in_deleted_group)?;
430                        log.append(&new_merge_log);
431                        continue;
432                    }
433                }
434                // The group already exists and is at the right location, so we can proceed and merge
435                // the two groups.
436                let new_merge_log = self.merge_group(&new_group_location, other_group, is_in_deleted_group)?;
437                log.append(&new_merge_log);
438                continue;
439            }
440            // The group doesn't exist in the destination, we create it
441            // let mut new_group = other_group.to_owned().clone();
442            let new_group = other_group.borrow().duplicate();
443            // new_group.children = vec![];
444            with_node_mut::<Group, _, _>(&new_group, |g| g.reset_children(vec![])).unwrap();
445            log.events.push(MergeEvent {
446                event_type: MergeEventType::GroupCreated,
447                node_uuid: new_group.borrow().get_uuid(),
448            });
449            let new_group_parent_group =
450                Group::find_group(&self.root, current_group_path).ok_or(MergeError::FindGroupError(current_group_path.to_owned()))?;
451            with_node_mut::<Group, _, _>(&new_group_parent_group, |g| g.add_child(new_group, 0)).unwrap();
452            let new_merge_log = self.merge_group(&new_group_location, other_group, is_in_deleted_group)?;
453            log.append(&new_merge_log);
454        }
455        Ok(log)
456    }
457
458    fn relocate_node(
459        &self,
460        node_uuid: Uuid,
461        from: &[Uuid],
462        to: &[Uuid],
463        new_location_changed_timestamp: NaiveDateTime,
464    ) -> Result<(), MergeError> {
465        let source_group = Group::find_group(&self.root, from).ok_or(MergeError::FindGroupError(from.to_owned()))?;
466        let relocated_node = with_node_mut::<Group, _, _>(&source_group, |s| s.remove_node(node_uuid)).unwrap()?;
467        relocated_node
468            .borrow_mut()
469            .get_times_mut()
470            .set_location_changed(Some(new_location_changed_timestamp));
471
472        let destination_group = Group::find_group(&self.root, to).ok_or(MergeError::FindGroupError(to.to_owned()))?;
473        group_add_child(&destination_group, relocated_node, 0).unwrap();
474        Ok(())
475    }
476}
477
478pub(crate) fn has_diverged_from(node: &NodePtr, other_node: &NodePtr) -> bool {
479    if let Some(entry) = node.borrow().downcast_ref::<Entry>()
480        && let Some(other_entry) = other_node.borrow().downcast_ref::<Entry>()
481    {
482        return entry._has_diverged_from(other_entry);
483    }
484    if let Some(group) = node.borrow().downcast_ref::<Group>()
485        && let Some(other_group) = other_node.borrow().downcast_ref::<Group>()
486    {
487        return group._has_diverged_from(other_group);
488    }
489    false
490}
491
492impl Group {
493    pub(crate) fn find_group(group: &NodePtr, path: &[Uuid]) -> Option<NodePtr> {
494        let path: Vec<String> = path.iter().map(|p| p.to_string()).collect();
495        let node_ref = Self::get_by_uuid(group, &path)?;
496        if node_is_group(&node_ref) { Some(node_ref) } else { None }
497    }
498
499    pub(crate) fn find_entry(group: &NodePtr, path: &[Uuid]) -> Option<NodePtr> {
500        let path: Vec<String> = path.iter().map(|p| p.to_string()).collect();
501        let node_ref = Self::get_by_uuid(group, &path)?;
502        if node_is_entry(&node_ref) { Some(node_ref) } else { None }
503    }
504
505    pub(crate) fn remove_node(&mut self, uuid: Uuid) -> Result<NodePtr, MergeError> {
506        let mut removed_node = None;
507        self.children.retain(|c| {
508            if c.borrow().get_uuid() == uuid {
509                removed_node = Some(NodePtr::from(c));
510                return false;
511            }
512            true
513        });
514
515        let title = self.get_title().unwrap_or("No title").to_string();
516        let node = removed_node.ok_or(MergeError::GenericError(format!("Could not find node {uuid} in group \"{title}\"")))?;
517        Ok(node)
518    }
519
520    pub(crate) fn find_node_location(parent: &NodePtr, id: Uuid) -> Option<Vec<Uuid>> {
521        let parent_uuid = parent.borrow().get_uuid();
522        let mut current_location = vec![parent_uuid];
523        for node in &group_get_children(parent).unwrap_or_default() {
524            let node_uuid = node.borrow().get_uuid();
525            if node_is_entry(node) {
526                if node_uuid == id {
527                    // current_location.push(id);
528                    return Some(current_location);
529                }
530            } else if node_is_group(node) {
531                if node_uuid == id {
532                    // current_location.push(id);
533                    return Some(current_location);
534                }
535                if let Some(mut location) = Self::find_node_location(node, id) {
536                    current_location.append(&mut location);
537                    return Some(current_location);
538                }
539            }
540        }
541        None
542    }
543
544    pub(crate) fn merge_with(group: &NodePtr, other: &NodePtr) -> Result<MergeLog, MergeError> {
545        let mut log = MergeLog::default();
546
547        let group_uuid = group.borrow().get_uuid();
548
549        let other = other.borrow();
550        let other = other
551            .downcast_ref::<Group>()
552            .ok_or(MergeError::GenericError("Could not downcast node to group".to_string()))?;
553        let source_last_modification = match other.times.get_last_modification() {
554            Some(t) => t,
555            None => {
556                log.warnings
557                    .push(format!("Group {group_uuid} did not have a last modification timestamp"));
558                Times::epoch()
559            }
560        };
561        let destination_last_modification = match group.borrow().get_times().get_last_modification() {
562            Some(t) => t,
563            None => {
564                log.warnings
565                    .push(format!("Group {group_uuid} did not have a last modification timestamp"));
566                Times::now()
567            }
568        };
569        if destination_last_modification == source_last_modification {
570            if group.borrow().downcast_ref::<Group>().unwrap()._has_diverged_from(other) {
571                // This should never happen.
572                // This means that a group was updated without updating the last modification
573                // timestamp.
574                return Err(MergeError::GroupModificationTimeNotUpdated(other.uuid.to_string()));
575            }
576            return Ok(log);
577        }
578        if destination_last_modification > source_last_modification {
579            return Ok(log);
580        }
581        with_node_mut::<Group, _, _>(group, |group| {
582            group.name = other.name.clone();
583            group.notes = other.notes.clone();
584            group.icon_id = other.icon_id;
585            group.custom_icon_uuid = other.custom_icon_uuid;
586            group.custom_data = other.custom_data.clone();
587            // The location changed timestamp is handled separately when merging two databases.
588            let current_times = group.times.clone();
589            group.times = other.times.clone();
590            if let Some(t) = current_times.get_location_changed() {
591                group.times.set_location_changed(Some(t));
592            }
593            group.is_expanded = other.is_expanded;
594            group.default_autotype_sequence = other.default_autotype_sequence.clone();
595            group.enable_autotype = other.enable_autotype;
596            group.enable_searching = other.enable_searching;
597            group.last_top_visible_entry = other.last_top_visible_entry;
598        })
599        .unwrap();
600        log.events.push(MergeEvent {
601            event_type: MergeEventType::GroupUpdated,
602            node_uuid: group_uuid,
603        });
604        Ok(log)
605    }
606
607    pub(crate) fn _has_diverged_from(&self, other: &Group) -> bool {
608        let new_times = Times::new();
609        let mut self_purged = self.clone();
610        self_purged.times = new_times.clone();
611        self_purged.children = vec![];
612        let mut other_purged = other.clone();
613        other_purged.times = new_times.clone();
614        other_purged.children = vec![];
615        !self_purged.eq(&other_purged)
616    }
617
618    fn replace_entry(root: &NodePtr, entry: &NodePtr) -> bool {
619        let uuid = entry.borrow().get_uuid();
620        if let Some(target_entry) = search_node_by_uuid_with_specific_type::<Entry>(root, uuid) {
621            return with_node_mut::<Entry, _, _>(&target_entry, |e| e.replaced_with(entry)).unwrap_or(false);
622        }
623        false
624    }
625
626    pub(crate) fn has_group(&self, uuid: Uuid) -> bool {
627        self.children.iter().any(|n| n.borrow().get_uuid() == uuid && node_is_group(n))
628    }
629
630    fn get_or_create_group(group: &NodePtr, location: &[Uuid], create_groups: bool) -> crate::Result<NodePtr> {
631        if location.is_empty() {
632            return Err("Empty location.".into());
633        }
634
635        let mut remaining_location = location.to_owned();
636        remaining_location.remove(0);
637
638        if remaining_location.is_empty() {
639            return Ok(group.clone());
640        }
641
642        let next_location = &remaining_location[0];
643        let mut next_location_uuid = *next_location;
644
645        if !with_node::<Group, _, _>(group, |g| g.has_group(next_location_uuid)).unwrap() && create_groups {
646            let mut current_group: Option<NodePtr> = None;
647            for i in (0..(remaining_location.len())).rev() {
648                let mut new_group = Group::new(&remaining_location[i].to_string());
649                new_group.set_uuid(remaining_location[i]);
650                if let Some(current_group) = current_group {
651                    let count = group_get_children(group).map(|c| c.len()).unwrap_or(0);
652                    new_group.add_child(current_group, count);
653                }
654                current_group = Some(rc_refcell_node(new_group));
655            }
656
657            if let Some(current_group) = current_group {
658                next_location_uuid = current_group.borrow().get_uuid();
659                let count = group_get_children(group).map_or(0, |c| c.len());
660                group_add_child(group, current_group, count)?;
661            } else {
662                return Err("Could not create group.".into());
663            }
664        }
665
666        let mut target = None;
667        for node in group_get_children(group).unwrap_or_default().iter() {
668            if node_is_group(node) && node.borrow().get_uuid() == next_location_uuid {
669                target = Some(node.clone());
670                break;
671            }
672        }
673
674        match &target {
675            Some(target) => Self::get_or_create_group(target, &remaining_location, create_groups),
676            None => Err("The group was not found.".into()),
677        }
678    }
679
680    pub(crate) fn insert_entry(group: &NodePtr, entry: NodePtr, location: &[Uuid]) -> crate::Result<()> {
681        let group = Self::get_or_create_group(group, location, true)?;
682        with_node_mut::<Group, _, _>(&group, |g| {
683            let count = g.children.len();
684            g.add_child(entry, count);
685            Ok::<(), crate::Error>(())
686        })
687        .ok_or("Could not add entry")??;
688        Ok(())
689    }
690
691    pub(crate) fn remove_entry(group: &NodePtr, uuid: Uuid, location: &[Uuid]) -> crate::Result<NodePtr> {
692        let group = Self::get_or_create_group(group, location, false)?;
693
694        let mut removed_entry: Option<NodePtr> = None;
695        let mut new_nodes: Vec<NodePtr> = vec![];
696        println!(
697            "Searching for entry {} in {}",
698            uuid,
699            group.borrow().get_title().unwrap_or("No title")
700        );
701
702        with_node::<Group, _, _>(&group, |g| {
703            for node in g.children.iter() {
704                if node_is_entry(node) {
705                    let node_uuid = node.borrow().get_uuid();
706                    println!("Saw entry {node_uuid}");
707                    if node_uuid != uuid {
708                        new_nodes.push(NodePtr::from(node));
709                        continue;
710                    }
711                    removed_entry = Some(NodePtr::from(node));
712                } else if node_is_group(node) {
713                    new_nodes.push(NodePtr::from(node));
714                }
715            }
716        });
717
718        if let Some(entry) = removed_entry {
719            with_node_mut::<Group, _, _>(&group, |g| g.reset_children(new_nodes)).ok_or("Could not reset children")?;
720            Ok(entry)
721        } else {
722            let title = group.borrow().get_title().unwrap_or("No title").to_string();
723            Err(format!("Could not find entry {uuid} in group \"{title}\".").into())
724        }
725    }
726
727    pub(crate) fn find_entry_location(&self, uuid: Uuid) -> Option<Vec<Uuid>> {
728        let mut current_location = vec![self.uuid];
729        for node in &self.children {
730            if node_is_entry(node) {
731                if node.borrow().get_uuid() == uuid {
732                    return Some(current_location);
733                }
734            } else if let Some(g) = node.borrow().downcast_ref::<Group>()
735                && let Some(mut location) = g.find_entry_location(uuid)
736            {
737                current_location.append(&mut location);
738                return Some(current_location);
739            }
740        }
741        None
742    }
743
744    pub(crate) fn add_entry(parent: &NodePtr, entry: NodePtr, location: &[Uuid]) -> crate::Result<()> {
745        if location.is_empty() {
746            panic!("TODO handle this with a Response.");
747        }
748
749        let mut remaining_location = location.to_owned();
750        remaining_location.remove(0);
751
752        if remaining_location.is_empty() {
753            with_node_mut::<Group, _, _>(parent, |g| {
754                let count = g.children.len();
755                g.add_child(entry, count);
756                Ok::<(), crate::Error>(())
757            })
758            .ok_or("Could not add entry")??;
759            return Ok(());
760        }
761
762        let next_location = remaining_location[0];
763
764        println!("Searching for group {next_location:?}");
765        for node in group_get_children(parent).unwrap_or_default() {
766            if node_is_group(&node) {
767                if node.borrow().get_uuid() != next_location {
768                    continue;
769                }
770                Self::add_entry(&node, entry, &remaining_location)?;
771                return Ok(());
772            }
773        }
774
775        // The group was not found, so we create it.
776        let new_group = rc_refcell_node(Group::new(&next_location.to_string()));
777        new_group.borrow_mut().set_uuid(next_location);
778        Self::add_entry(&new_group, entry, &remaining_location)?;
779        let count = group_get_children(parent).map_or(0, |c| c.len());
780        group_add_child(parent, new_group, count)?;
781        Ok(())
782    }
783
784    /// Merge this group with another group
785    #[allow(clippy::too_many_lines)]
786    pub fn merge(root: &NodePtr, other_group: &NodePtr) -> crate::Result<MergeLog> {
787        let mut log = MergeLog::default();
788
789        let other_entries = with_node::<Group, _, _>(other_group, |g| Ok(g.get_all_entries(&[])))
790            .unwrap_or(Err(crate::Error::from("Could not downcast other group to group")))?;
791
792        // Handle entry relocation.
793        for (entry, entry_location) in &other_entries {
794            let entry_uuid = entry.borrow().get_uuid();
795            let the_entry = search_node_by_uuid_with_specific_type::<Entry>(root, entry_uuid);
796
797            let existing_entry = match the_entry {
798                Some(e) => e,
799                None => continue,
800            };
801
802            let the_entry_location = with_node::<Group, _, _>(root, |g| Ok(g.find_entry_location(entry_uuid)))
803                .unwrap_or(Err("Could not downcast root to group"))?;
804
805            let existing_entry_location = match the_entry_location {
806                Some(l) => l,
807                None => continue,
808            };
809
810            let source_location_changed_time = if let Some(t) = entry.borrow().get_times().get_location_changed() {
811                t
812            } else {
813                log.warnings
814                    .push(format!("Entry {entry_uuid} did not have a location updated timestamp"));
815                Times::epoch()
816            };
817            let destination_location_changed = if let Some(t) = existing_entry.borrow().get_times().get_location_changed() {
818                t
819            } else {
820                log.warnings
821                    .push(format!("Entry {entry_uuid} did not have a location updated timestamp"));
822                Times::now()
823            };
824            if source_location_changed_time > destination_location_changed {
825                log.events.push(MergeEvent {
826                    event_type: MergeEventType::EntryLocationUpdated,
827                    node_uuid: entry_uuid,
828                });
829                Self::remove_entry(root, entry_uuid, &existing_entry_location)?;
830                Self::insert_entry(root, entry.borrow().duplicate(), entry_location)?;
831            }
832        }
833
834        // Handle entry updates
835        for (entry, entry_location) in &other_entries {
836            let entry_uuid = entry.borrow().get_uuid();
837            let the_entry = search_node_by_uuid_with_specific_type::<Entry>(root, entry_uuid);
838            if let Some(existing_entry) = the_entry {
839                if node_is_equals_to(&existing_entry, entry) {
840                    continue;
841                }
842
843                let source_last_modification = if let Some(t) = entry.borrow().get_times().get_last_modification() {
844                    t
845                } else {
846                    log.warnings
847                        .push(format!("Entry {entry_uuid} did not have a last modification timestamp"));
848                    Times::epoch()
849                };
850                let destination_last_modification = if let Some(t) = existing_entry.borrow().get_times().get_last_modification() {
851                    t
852                } else {
853                    log.warnings
854                        .push(format!("Entry {entry_uuid} did not have a last modification timestamp"));
855                    Times::now()
856                };
857
858                if destination_last_modification == source_last_modification {
859                    if !node_is_equals_to(&existing_entry, entry) {
860                        // This should never happen.
861                        // This means that an entry was updated without updating the last modification
862                        // timestamp.
863                        return Err("Entries have the same modification time but are not the same!".into());
864                    }
865                    continue;
866                }
867
868                let (merged_entry, entry_merge_log) = if destination_last_modification > source_last_modification {
869                    Entry::merge(&existing_entry, entry)?
870                } else {
871                    Entry::merge(entry, &existing_entry)?
872                };
873                let Some(merged_entry) = merged_entry else {
874                    continue;
875                };
876                // merged_entry.borrow_mut().set_parent(existing_entry.borrow().get_parent());
877                if node_is_equals_to(&existing_entry, &merged_entry) {
878                    continue;
879                }
880
881                Group::replace_entry(root, &merged_entry);
882
883                log.events.push(MergeEvent {
884                    event_type: MergeEventType::EntryUpdated,
885                    node_uuid: merged_entry.borrow().get_uuid(),
886                });
887                log = log.merge_with(&entry_merge_log);
888            } else {
889                Self::add_entry(root, entry.borrow().duplicate(), entry_location)?;
890                // TODO should we update the time info for the entry?
891                log.events.push(MergeEvent {
892                    event_type: MergeEventType::EntryCreated,
893                    node_uuid: entry.borrow().get_uuid(),
894                });
895            }
896        }
897
898        // TODO handle deleted objects
899        Ok(log)
900    }
901
902    // Recursively get all the entries in the group, along with their
903    // location.
904    pub(crate) fn get_all_entries(&self, current_location: &[Uuid]) -> Vec<(NodePtr, Vec<Uuid>)> {
905        let mut response: Vec<(NodePtr, Vec<Uuid>)> = vec![];
906        let mut new_location = current_location.to_owned();
907        new_location.push(self.uuid);
908
909        for node in &self.children {
910            if node_is_entry(node) {
911                response.push((node.into(), new_location.clone()));
912            }
913            with_node::<Group, _, _>(node, |g| {
914                let mut new_entries = g.get_all_entries(&new_location);
915                response.append(&mut new_entries);
916            });
917        }
918        response
919    }
920}
921
922#[cfg(test)]
923pub fn entry_set_field_and_commit(entry: &NodePtr, field_name: &str, field_value: &str) -> crate::Result<()> {
924    with_node_mut::<Entry, _, _>(entry, |entry| {
925        entry.set_field_and_commit(field_name, field_value);
926        Ok(())
927    })
928    .unwrap_or(Err("node is not an Entry.".to_string()))?;
929    Ok(())
930}
931
932impl Entry {
933    pub(crate) fn merge(entry: &NodePtr, other: &NodePtr) -> Result<(Option<NodePtr>, MergeLog), MergeError> {
934        let mut log = MergeLog::default();
935        let source_last_modification = match with_node::<Entry, _, _>(other, |e| e.get_times().get_last_modification()).unwrap() {
936            Some(t) => t,
937            None => {
938                let info = format!("Entry {} did not have a last modification timestamp", other.borrow().get_uuid());
939                log.warnings.push(info);
940                Times::epoch()
941            }
942        };
943
944        let destination_last_modification = match with_node::<Entry, _, _>(entry, |e| e.get_times().get_last_modification()).unwrap() {
945            Some(t) => t,
946            None => {
947                let info = format!("Entry {} did not have a last modification timestamp", entry.borrow().get_uuid());
948                log.warnings.push(info);
949                Times::epoch()
950            }
951        };
952
953        if destination_last_modification == source_last_modification {
954            if !crate::db::merge::has_diverged_from(entry, other) {
955                // This should never happen. This means that an entry was updated without updating the last modification timestamp.
956                return Err(MergeError::EntryModificationTimeNotUpdated(other.borrow().get_uuid().to_string()));
957            }
958            return Ok((None, log));
959        }
960        let (mut merged_entry, entry_merge_log) = with_node::<Entry, _, _>(entry, |entry| {
961            with_node::<Entry, _, _>(other, |other| {
962                if destination_last_modification > source_last_modification {
963                    entry.merge_history(other)
964                } else {
965                    other.merge_history(entry)
966                }
967            })
968            .unwrap()
969        })
970        .unwrap()?;
971
972        if let location_changed_timestamp @ Some(_) = entry.borrow().get_times().get_location_changed() {
973            merged_entry.get_times_mut().set_location_changed(location_changed_timestamp);
974        }
975        Ok((Some(rc_refcell_node(merged_entry)), entry_merge_log))
976    }
977
978    pub(crate) fn merge_history(&self, other: &Entry) -> Result<(Entry, MergeLog), MergeError> {
979        let mut log = MergeLog::default();
980        let mut source_history = match &other.history {
981            Some(h) => h.clone(),
982            None => {
983                log.warnings
984                    .push(format!("Entry {} from source database had no history.", self.uuid));
985                History::default()
986            }
987        };
988        let mut destination_history = match &self.history {
989            Some(h) => h.clone(),
990            None => {
991                log.warnings
992                    .push(format!("Entry {} from destination database had no history.", self.uuid));
993                History::default()
994            }
995        };
996        let mut response = self.clone();
997        if other.has_uncommited_changes() {
998            log.warnings
999                .push(format!("Entry {} from source database has uncommitted changes.", self.uuid));
1000            source_history.add_entry(other.clone());
1001        }
1002        // TODO we should probably check for uncommitted changes in the destination
1003        // database here too for consistency.
1004        let history_merge_log = destination_history.merge_with(&source_history)?;
1005        response.history = Some(destination_history);
1006        Ok((response, log.merge_with(&history_merge_log)))
1007    }
1008
1009    // Convenience function used in when merging two entries
1010    pub(crate) fn _has_diverged_from(&self, other_entry: &Entry) -> bool {
1011        let new_times = Times::default();
1012
1013        let mut self_without_times = self.clone();
1014        self_without_times.times = new_times.clone();
1015
1016        let mut other_without_times = other_entry.clone();
1017        other_without_times.times = new_times;
1018        !self_without_times.eq(&other_without_times)
1019    }
1020
1021    /*
1022    pub(crate) fn merge_(entry: &NodePtr, other: &NodePtr) -> Result<(NodePtr, MergeLog), String> {
1023        let mut log = MergeLog::default();
1024
1025        let mut source_history = match &other.borrow().as_any().downcast_ref::<Entry>().ok_or("Error")?.history {
1026            Some(h) => h.clone(),
1027            None => {
1028                log.warnings.push(format!("Entry {} had no history.", entry.borrow().get_uuid()));
1029                History::default()
1030            }
1031        };
1032        let _destination_history = with_node::<Entry, _, _>(entry, |entry| entry.history.clone()).unwrap_or_default();
1033        let mut destination_history = _destination_history.unwrap_or_else(|| {
1034            log.warnings.push(format!("Entry {} had no history.", entry.borrow().get_uuid()));
1035            History::default()
1036        });
1037
1038        let other = other.borrow().duplicate();
1039        source_history.add_entry(other.borrow().as_any().downcast_ref::<Entry>().ok_or("Error")?.clone());
1040        let history_merge_log = destination_history.merge_with(&source_history)?;
1041        let response = entry.borrow().duplicate();
1042        with_node_mut::<Entry, _, _>(&response, |entry| {
1043            entry.history = Some(destination_history);
1044        });
1045
1046        Ok((response, log.merge_with(&history_merge_log)))
1047    }
1048    */
1049
1050    // Convenience function used in unit tests, to make sure that:
1051    // 1. The history gets updated after changing a field
1052    // 2. We wait a second before commiting the changes so that the timestamp is not the same
1053    //    as it previously was. This is necessary since the timestamps in the KDBX format
1054    //    do not preserve the msecs.
1055    #[cfg(test)]
1056    pub(crate) fn set_field_and_commit(&mut self, field_name: &str, field_value: &str) {
1057        self.set_unprotected_field_pair(field_name, Some(field_value));
1058        std::thread::sleep(std::time::Duration::from_secs(1));
1059        self.update_history();
1060    }
1061
1062    pub(crate) fn replaced_with(&mut self, other: &NodePtr) -> bool {
1063        let mut success = false;
1064        with_node::<Entry, _, _>(other, |other| {
1065            self.uuid = other.uuid;
1066            self.fields = other.fields.clone();
1067            self.autotype = other.autotype.clone();
1068            self.tags = other.tags.clone();
1069            self.times = other.times.clone();
1070            self.custom_data = other.custom_data.clone();
1071            self.icon_id = other.icon_id;
1072            self.custom_icon = other.custom_icon;
1073            self.foreground_color = other.foreground_color;
1074            self.background_color = other.background_color;
1075            self.override_url = other.override_url.clone();
1076            self.quality_check = other.quality_check;
1077            self.history = other.history.clone();
1078            // self.parent = other.parent;
1079            success = true;
1080        });
1081        success
1082    }
1083}
1084
1085impl History {
1086    // Determines if the entries of the history are
1087    // ordered by last modification time.
1088    #[cfg(test)]
1089    pub(crate) fn is_ordered(&self) -> bool {
1090        let mut last_modification_time: Option<chrono::NaiveDateTime> = None;
1091        for entry in &self.entries {
1092            if last_modification_time.is_none() {
1093                last_modification_time = entry.times.get_last_modification();
1094            }
1095
1096            let entry_modification_time = entry.times.get_last_modification().unwrap();
1097            // FIXME should we also handle equal modification times??
1098            if last_modification_time.unwrap() < entry_modification_time {
1099                return false;
1100            }
1101            last_modification_time = Some(entry_modification_time);
1102        }
1103        true
1104    }
1105
1106    // Merge both histories together.
1107    pub(crate) fn merge_with(&mut self, other: &History) -> Result<MergeLog, MergeError> {
1108        let mut log = MergeLog::default();
1109        let mut new_history_entries: HashMap<chrono::NaiveDateTime, Entry> = HashMap::new();
1110
1111        for history_entry in &self.entries {
1112            let modification_time = history_entry.times.get_last_modification().unwrap();
1113            if new_history_entries.contains_key(&modification_time) {
1114                return Err(MergeError::DuplicateHistoryEntries(
1115                    modification_time.to_string(),
1116                    history_entry.uuid.to_string(),
1117                ));
1118            }
1119            new_history_entries.insert(modification_time, history_entry.clone());
1120        }
1121
1122        for history_entry in &other.entries {
1123            let modification_time = history_entry.times.get_last_modification().unwrap();
1124            let existing_history_entry = new_history_entries.get(&modification_time);
1125            if let Some(existing_history_entry) = existing_history_entry {
1126                if existing_history_entry._has_diverged_from(history_entry) {
1127                    log.warnings.push(format!(
1128                        "History entries for {} have the same modification timestamp but were not the same.",
1129                        existing_history_entry.uuid
1130                    ));
1131                }
1132            } else {
1133                new_history_entries.insert(modification_time, history_entry.clone());
1134            }
1135        }
1136
1137        let mut all_modification_times: Vec<&chrono::NaiveDateTime> = new_history_entries.keys().collect();
1138        all_modification_times.sort();
1139        all_modification_times.reverse();
1140        let mut new_entries: Vec<Entry> = vec![];
1141        for modification_time in &all_modification_times {
1142            new_entries.push(new_history_entries.get(modification_time).unwrap().clone());
1143        }
1144        self.entries = new_entries;
1145        Ok(log)
1146    }
1147}
1148
1149#[cfg(test)]
1150mod merge_tests {
1151    use std::{thread, time};
1152    use uuid::Uuid;
1153
1154    use crate::db::{
1155        CustomIcon, Database, Entry, Group, Node, NodePtr, Times, group_add_child, group_get_children, node_is_group, rc_refcell_node,
1156        search_node_by_uuid_with_specific_type, with_node, with_node_mut,
1157    };
1158
1159    fn get_entry(db: &Database, path: &[&str]) -> NodePtr {
1160        Group::get(&db.root, path).unwrap()
1161    }
1162
1163    fn get_group(db: &Database, path: &[&str]) -> NodePtr {
1164        Group::get(&db.root, path).unwrap()
1165    }
1166
1167    fn get_all_groups(group: &NodePtr) -> Vec<NodePtr> {
1168        let mut response: Vec<NodePtr> = vec![];
1169        for node in group_get_children(group).unwrap() {
1170            if node_is_group(&node) {
1171                let mut new_groups = get_all_groups(&node);
1172                response.append(&mut new_groups);
1173                response.push(node);
1174            }
1175        }
1176
1177        response
1178    }
1179
1180    fn get_all_entries(group: &NodePtr) -> Vec<NodePtr> {
1181        let mut response: Vec<NodePtr> = vec![];
1182        for node in group_get_children(group).unwrap() {
1183            if node_is_group(&node) {
1184                let mut new_entries = get_all_entries(&node);
1185                response.append(&mut new_entries);
1186            } else {
1187                response.push(node);
1188            }
1189        }
1190        response
1191    }
1192
1193    const ROOT_GROUP_ID: &str = "00000000-0000-0000-0000-000000000001";
1194    const GROUP1_ID: &str = "00000000-0000-0000-0000-000000000002";
1195    const GROUP2_ID: &str = "00000000-0000-0000-0000-000000000003";
1196    const SUBGROUP1_ID: &str = "00000000-0000-0000-0000-000000000004";
1197    const SUBGROUP2_ID: &str = "00000000-0000-0000-0000-000000000005";
1198
1199    const ENTRY1_ID: &str = "00000000-0000-0000-0000-000000000006";
1200    const ENTRY2_ID: &str = "00000000-0000-0000-0000-000000000007";
1201
1202    fn create_test_database() -> Database {
1203        let mut db = Database::new(Default::default());
1204        let mut root_group = Group::new("root");
1205        root_group.uuid = Uuid::parse_str(ROOT_GROUP_ID).unwrap();
1206
1207        let mut group1 = Group::new("group1");
1208        group1.uuid = Uuid::parse_str(GROUP1_ID).unwrap();
1209        let mut group2 = Group::new("group2");
1210        group2.uuid = Uuid::parse_str(GROUP2_ID).unwrap();
1211
1212        let mut subgroup1 = Group::new("subgroup1");
1213        subgroup1.uuid = Uuid::parse_str(SUBGROUP1_ID).unwrap();
1214        let mut subgroup2 = Group::new("subgroup2");
1215        subgroup2.uuid = Uuid::parse_str(SUBGROUP2_ID).unwrap();
1216
1217        // Placing the first entry in the root group
1218        let mut entry1 = Entry::default();
1219        entry1.set_uuid(Uuid::parse_str(ENTRY1_ID).unwrap());
1220        entry1.set_field_and_commit("Title", "entry1");
1221        root_group.add_child(rc_refcell_node(entry1), 0);
1222
1223        // Placing the second entry in a subgroup
1224        let mut entry2 = Entry::default();
1225        entry2.set_uuid(Uuid::parse_str(ENTRY2_ID).unwrap());
1226        entry2.set_field_and_commit("Title", "entry2");
1227        subgroup1.add_child(rc_refcell_node(entry2), 0);
1228
1229        group1.add_child(rc_refcell_node(subgroup1), 0);
1230        group2.add_child(rc_refcell_node(subgroup2), 0);
1231
1232        root_group.add_child(rc_refcell_node(group1), 1);
1233        root_group.add_child(rc_refcell_node(group2), 2);
1234
1235        db.root = rc_refcell_node(root_group).into();
1236        db
1237    }
1238
1239    #[test]
1240    fn test_idempotence() {
1241        let mut destination_db = create_test_database();
1242        let source_db = destination_db.clone();
1243
1244        let entry_count_before = get_all_entries(&destination_db.root).len();
1245        let group_count_before = get_all_groups(&destination_db.root).len();
1246
1247        let merge_result = destination_db.merge(&source_db).unwrap();
1248        assert_eq!(merge_result.warnings.len(), 0);
1249        assert_eq!(merge_result.events.len(), 0);
1250        assert_eq!(group_get_children(&destination_db.root).unwrap().len(), 3);
1251
1252        // The 2 groups should be exactly the same after merging, since
1253        // nothing was performed during the merge.
1254        assert_eq!(destination_db, source_db);
1255
1256        let entry_count_after = get_all_entries(&destination_db.root).len();
1257        let group_count_after = get_all_groups(&destination_db.root).len();
1258        assert_eq!(entry_count_after, entry_count_before);
1259        assert_eq!(group_count_after, group_count_before);
1260
1261        let entry = get_all_entries(&destination_db.root)[0].clone();
1262        with_node_mut::<Entry, _, _>(&entry, |entry| {
1263            entry.set_field_and_commit("Title", "entry1_updated");
1264        });
1265
1266        let merge_result = destination_db.merge(&source_db).unwrap();
1267        assert_eq!(merge_result.warnings.len(), 0);
1268        assert_eq!(merge_result.events.len(), 0);
1269        let destination_db_just_after_merge = destination_db.clone();
1270
1271        let merge_result = destination_db.merge(&source_db).unwrap();
1272        assert_eq!(merge_result.warnings.len(), 0);
1273        assert_eq!(merge_result.events.len(), 0);
1274        // Merging twice in a row, even if the first merge updated the destination group,
1275        // should not create more changes.
1276        assert_eq!(destination_db_just_after_merge, destination_db);
1277    }
1278
1279    #[test]
1280    fn test_add_new_entry() {
1281        let mut destination_db = create_test_database();
1282        let source_db = destination_db.clone();
1283
1284        let entry_count_before = get_all_entries(&destination_db.root).len();
1285        let group_count_before = get_all_groups(&destination_db.root).len();
1286
1287        let mut new_entry = Entry::default();
1288        new_entry.set_field_and_commit("Title", "new_entry");
1289        group_add_child(&source_db.root, rc_refcell_node(new_entry), 0).unwrap();
1290
1291        let merge_result = destination_db.merge(&source_db).unwrap();
1292        assert_eq!(merge_result.warnings.len(), 0);
1293        assert_eq!(merge_result.events.len(), 1);
1294
1295        let entry_count_after = get_all_entries(&destination_db.root).len();
1296        let group_count_after = get_all_groups(&destination_db.root).len();
1297        assert_eq!(entry_count_after, entry_count_before + 1);
1298        assert_eq!(group_count_after, group_count_before);
1299
1300        let root_entries = with_node::<Group, _, _>(&destination_db.root, |group| group.entries()).unwrap();
1301        assert_eq!(root_entries.len(), 2);
1302
1303        let new_entry = get_entry(&destination_db, &["new_entry"]);
1304        assert_eq!(new_entry.borrow().get_title().unwrap(), "new_entry".to_string());
1305
1306        // Merging the same group again should not create a duplicate entry.
1307        let merge_result = destination_db.merge(&source_db).unwrap();
1308        assert_eq!(merge_result.warnings.len(), 0);
1309        assert_eq!(merge_result.events.len(), 0);
1310
1311        let entry_count_after = get_all_entries(&destination_db.root).len();
1312        let group_count_after = get_all_groups(&destination_db.root).len();
1313        assert_eq!(entry_count_after, entry_count_before + 1);
1314        assert_eq!(group_count_after, group_count_before);
1315    }
1316
1317    #[test]
1318    fn test_deleted_entry_in_destination() {
1319        let mut destination_db = create_test_database();
1320        let source_db = destination_db.clone();
1321
1322        let entry_count_before = get_all_entries(&destination_db.root).len();
1323        let group_count_before = get_all_groups(&destination_db.root).len();
1324
1325        let mut deleted_entry = Entry::default();
1326        let deleted_entry_uuid = deleted_entry.uuid;
1327        deleted_entry.set_field_and_commit("Title", "deleted_entry");
1328        group_add_child(&source_db.root, rc_refcell_node(deleted_entry), 0).unwrap();
1329
1330        destination_db.deleted_objects.insert(deleted_entry_uuid, Some(Times::now()));
1331
1332        let merge_result = destination_db.merge(&source_db).unwrap();
1333        assert_eq!(merge_result.warnings.len(), 0);
1334        assert_eq!(merge_result.events.len(), 0);
1335
1336        let entry_count_after = get_all_entries(&destination_db.root).len();
1337        let group_count_after = get_all_groups(&destination_db.root).len();
1338        assert_eq!(entry_count_after, entry_count_before);
1339        assert_eq!(group_count_after, group_count_before);
1340
1341        let new_entry = Group::find_node_location(&destination_db.root, deleted_entry_uuid);
1342        assert!(new_entry.is_none());
1343    }
1344
1345    #[test]
1346    fn test_updated_entry_under_deleted_group() {
1347        let mut destination_db = create_test_database();
1348        let source_db = destination_db.clone();
1349
1350        let mut modified_entry = Entry::default();
1351        modified_entry.set_field_and_commit("Title", "original_title");
1352        group_add_child(&destination_db.root, modified_entry.duplicate(), 0).unwrap();
1353
1354        let mut deleted_group = Group::new("deleted_group");
1355        let deleted_group_uuid = deleted_group.uuid;
1356        let modified_entry_uuid = modified_entry.uuid;
1357        modified_entry.set_field_and_commit("Title", "modified_title");
1358        deleted_group.add_child(rc_refcell_node(modified_entry), 0);
1359        group_add_child(&source_db.root, rc_refcell_node(deleted_group), 0).unwrap();
1360
1361        let entry_count_before = get_all_entries(&destination_db.root).len();
1362        let group_count_before = get_all_groups(&destination_db.root).len();
1363
1364        destination_db.deleted_objects.insert(deleted_group_uuid, Some(Times::now()));
1365
1366        let merge_result = destination_db.merge(&source_db).unwrap();
1367        assert_eq!(merge_result.warnings.len(), 0);
1368        assert_eq!(merge_result.events.len(), 1);
1369
1370        let entry_count_after = get_all_entries(&destination_db.root).len();
1371        let group_count_after = get_all_groups(&destination_db.root).len();
1372        assert_eq!(entry_count_after, entry_count_before);
1373        assert_eq!(group_count_after, group_count_before);
1374
1375        let deleted_group = Group::find_node_location(&destination_db.root, deleted_group_uuid);
1376        assert!(deleted_group.is_none());
1377
1378        let modified_entry_location = Group::find_node_location(&destination_db.root, modified_entry_uuid);
1379        assert!(modified_entry_location.is_some());
1380
1381        let modified_entry = Group::find_entry(&destination_db.root, &[modified_entry_uuid]).unwrap();
1382        assert_eq!(modified_entry.borrow().get_title(), Some("modified_title"));
1383    }
1384
1385    #[test]
1386    fn test_deleted_group_in_destination() {
1387        let mut destination_db = create_test_database();
1388        let source_db = destination_db.clone();
1389
1390        let entry_count_before = get_all_entries(&destination_db.root).len();
1391        let group_count_before = get_all_groups(&destination_db.root).len();
1392
1393        let deleted_group = Group::new("deleted_group");
1394        let deleted_group_uuid = deleted_group.uuid;
1395        group_add_child(&source_db.root, rc_refcell_node(deleted_group), 0).unwrap();
1396
1397        destination_db.deleted_objects.insert(deleted_group_uuid, Some(Times::now()));
1398
1399        let merge_result = destination_db.merge(&source_db).unwrap();
1400        assert_eq!(merge_result.warnings.len(), 0);
1401        assert_eq!(merge_result.events.len(), 0);
1402
1403        let entry_count_after = get_all_entries(&destination_db.root).len();
1404        let group_count_after = get_all_groups(&destination_db.root).len();
1405        assert_eq!(entry_count_after, entry_count_before);
1406        assert_eq!(group_count_after, group_count_before);
1407
1408        let deleted_group = Group::find_node_location(&destination_db.root, deleted_group_uuid);
1409        assert!(deleted_group.is_none());
1410    }
1411
1412    #[test]
1413    fn test_deleted_entry_in_source() {
1414        let mut destination_db = create_test_database();
1415        let mut source_db = destination_db.clone();
1416
1417        let mut deleted_entry = Entry::default();
1418        let deleted_entry_uuid = deleted_entry.uuid;
1419        deleted_entry.set_field_and_commit("Title", "deleted_entry");
1420        group_add_child(&destination_db.root, rc_refcell_node(deleted_entry), 0).unwrap();
1421
1422        let entry_count_before = get_all_entries(&destination_db.root).len();
1423        let group_count_before = get_all_groups(&destination_db.root).len();
1424
1425        thread::sleep(time::Duration::from_secs(1));
1426        source_db.deleted_objects.insert(deleted_entry_uuid, Some(Times::now()));
1427
1428        let merge_result = destination_db.merge(&source_db).unwrap();
1429        assert_eq!(merge_result.warnings.len(), 0);
1430        assert_eq!(merge_result.events.len(), 1);
1431
1432        let entry_count_after = get_all_entries(&destination_db.root).len();
1433        let group_count_after = get_all_groups(&destination_db.root).len();
1434        assert_eq!(entry_count_after, entry_count_before - 1);
1435        assert_eq!(group_count_after, group_count_before);
1436
1437        let new_entry = Group::find_node_location(&destination_db.root, deleted_entry_uuid);
1438        assert!(new_entry.is_none());
1439
1440        assert!(destination_db.deleted_objects.contains_key(&deleted_entry_uuid));
1441    }
1442
1443    #[test]
1444    fn test_deleted_group_in_source() {
1445        let mut destination_db = create_test_database();
1446        let mut source_db = destination_db.clone();
1447
1448        let deleted_group = Group::new("deleted_group");
1449        let deleted_group_uuid = deleted_group.uuid;
1450        group_add_child(&destination_db.root, rc_refcell_node(deleted_group), 0).unwrap();
1451
1452        let entry_count_before = get_all_entries(&destination_db.root).len();
1453        let group_count_before = get_all_groups(&destination_db.root).len();
1454
1455        thread::sleep(time::Duration::from_secs(1));
1456        source_db.deleted_objects.insert(deleted_group_uuid, Some(Times::now()));
1457
1458        let merge_result = destination_db.merge(&source_db).unwrap();
1459        assert_eq!(merge_result.warnings.len(), 0);
1460        assert_eq!(merge_result.events.len(), 1);
1461
1462        let entry_count_after = get_all_entries(&destination_db.root).len();
1463        let group_count_after = get_all_groups(&destination_db.root).len();
1464        assert_eq!(entry_count_after, entry_count_before);
1465        assert_eq!(group_count_after, group_count_before - 1);
1466
1467        let deleted_group = Group::find_node_location(&destination_db.root, deleted_group_uuid);
1468        assert!(deleted_group.is_none());
1469
1470        assert!(destination_db.deleted_objects.contains_key(&deleted_group_uuid));
1471    }
1472
1473    #[test]
1474    fn test_deleted_entry_in_source_modified_in_destination() {
1475        let mut destination_db = create_test_database();
1476        let mut source_db = destination_db.clone();
1477
1478        let deleted_entry_uuid = Uuid::new_v4();
1479
1480        thread::sleep(time::Duration::from_secs(1));
1481        source_db.deleted_objects.insert(deleted_entry_uuid, Some(Times::now()));
1482
1483        thread::sleep(time::Duration::from_secs(1));
1484        let mut deleted_entry = Entry::default();
1485        deleted_entry.set_uuid(deleted_entry_uuid);
1486        deleted_entry.set_field_and_commit("Title", "deleted_entry");
1487        group_add_child(&destination_db.root, rc_refcell_node(deleted_entry), 0).unwrap();
1488
1489        let entry_count_before = get_all_entries(&destination_db.root).len();
1490        let group_count_before = get_all_groups(&destination_db.root).len();
1491
1492        let merge_result = destination_db.merge(&source_db).unwrap();
1493        assert_eq!(merge_result.warnings.len(), 0);
1494        assert_eq!(merge_result.events.len(), 0);
1495
1496        let entry_count_after = get_all_entries(&destination_db.root).len();
1497        let group_count_after = get_all_groups(&destination_db.root).len();
1498        assert_eq!(entry_count_after, entry_count_before);
1499        assert_eq!(group_count_after, group_count_before);
1500
1501        let new_entry = Group::find_node_location(&destination_db.root, deleted_entry_uuid);
1502        assert!(new_entry.is_some());
1503
1504        assert!(!destination_db.deleted_objects.contains_key(&deleted_entry_uuid));
1505    }
1506
1507    #[test]
1508    fn test_group_subtree_deletion() {
1509        let mut destination_db = create_test_database();
1510        let mut source_db = destination_db.clone();
1511
1512        let deleted_entry_uuid = Uuid::new_v4();
1513        let deleted_group_uuid = Uuid::new_v4();
1514        let deleted_subgroup_uuid = Uuid::new_v4();
1515
1516        thread::sleep(time::Duration::from_secs(1));
1517        let mut deleted_entry = Entry::default();
1518        deleted_entry.set_uuid(deleted_entry_uuid);
1519        deleted_entry.set_field_and_commit("Title", "deleted_entry");
1520
1521        let mut deleted_subgroup = Group::new("deleted_subgroup");
1522        deleted_subgroup.uuid = deleted_subgroup_uuid;
1523        deleted_subgroup.add_child(rc_refcell_node(deleted_entry), 0);
1524
1525        let mut deleted_group = Group::new("deleted_group");
1526        deleted_group.uuid = deleted_group_uuid;
1527        deleted_group.add_child(rc_refcell_node(deleted_subgroup), 0);
1528
1529        group_add_child(&destination_db.root, rc_refcell_node(deleted_group), 0).unwrap();
1530
1531        thread::sleep(time::Duration::from_secs(1));
1532        source_db.deleted_objects.insert(deleted_entry_uuid, Some(Times::now()));
1533        source_db.deleted_objects.insert(deleted_subgroup_uuid, Some(Times::now()));
1534        source_db.deleted_objects.insert(deleted_group_uuid, Some(Times::now()));
1535
1536        let entry_count_before = get_all_entries(&destination_db.root).len();
1537        let group_count_before = get_all_groups(&destination_db.root).len();
1538
1539        let merge_result = destination_db.merge(&source_db).unwrap();
1540        assert_eq!(merge_result.warnings.len(), 0);
1541        assert_eq!(merge_result.events.len(), 3);
1542
1543        let entry_count_after = get_all_entries(&destination_db.root).len();
1544        let group_count_after = get_all_groups(&destination_db.root).len();
1545        assert_eq!(entry_count_after, entry_count_before - 1);
1546        assert_eq!(group_count_after, group_count_before - 2);
1547
1548        let deleted_entry = Group::find_node_location(&destination_db.root, deleted_entry_uuid);
1549        assert!(deleted_entry.is_none());
1550        let deleted_subgroup = Group::find_node_location(&destination_db.root, deleted_subgroup_uuid);
1551        assert!(deleted_subgroup.is_none());
1552        let deleted_group = Group::find_node_location(&destination_db.root, deleted_group_uuid);
1553        assert!(deleted_group.is_none());
1554
1555        assert!(destination_db.deleted_objects.contains_key(&deleted_entry_uuid));
1556        assert!(destination_db.deleted_objects.contains_key(&deleted_subgroup_uuid));
1557        assert!(destination_db.deleted_objects.contains_key(&deleted_group_uuid));
1558    }
1559
1560    #[test]
1561    fn test_group_subtree_partial_deletion() {
1562        let mut destination_db = create_test_database();
1563        let mut source_db = destination_db.clone();
1564
1565        let deleted_entry_uuid = Uuid::new_v4();
1566        let deleted_group_uuid = Uuid::new_v4();
1567        let deleted_subgroup_uuid = Uuid::new_v4();
1568
1569        thread::sleep(time::Duration::from_secs(1));
1570        let mut deleted_entry = Entry::default();
1571        deleted_entry.set_uuid(deleted_entry_uuid);
1572        deleted_entry.set_field_and_commit("Title", "deleted_entry");
1573
1574        let mut deleted_subgroup = Group::new("deleted_subgroup");
1575        deleted_subgroup.uuid = deleted_subgroup_uuid;
1576        deleted_subgroup.add_child(rc_refcell_node(deleted_entry), 0);
1577
1578        thread::sleep(time::Duration::from_secs(1));
1579        source_db.deleted_objects.insert(deleted_entry_uuid, Some(Times::now()));
1580        source_db.deleted_objects.insert(deleted_subgroup_uuid, Some(Times::now()));
1581        source_db.deleted_objects.insert(deleted_group_uuid, Some(Times::now()));
1582
1583        thread::sleep(time::Duration::from_secs(1));
1584        let mut deleted_group = Group::new("deleted_group");
1585        deleted_group.uuid = deleted_group_uuid;
1586        deleted_group.add_child(rc_refcell_node(deleted_subgroup), 0);
1587
1588        group_add_child(&destination_db.root, rc_refcell_node(deleted_group), 0).unwrap();
1589
1590        let entry_count_before = get_all_entries(&destination_db.root).len();
1591        let group_count_before = get_all_groups(&destination_db.root).len();
1592
1593        let merge_result = destination_db.merge(&source_db).unwrap();
1594        assert_eq!(merge_result.warnings.len(), 0);
1595        assert_eq!(merge_result.events.len(), 2);
1596
1597        let entry_count_after = get_all_entries(&destination_db.root).len();
1598        let group_count_after = get_all_groups(&destination_db.root).len();
1599        assert_eq!(entry_count_after, entry_count_before - 1);
1600        assert_eq!(group_count_after, group_count_before - 1);
1601
1602        let deleted_entry = Group::find_node_location(&destination_db.root, deleted_entry_uuid);
1603        assert!(deleted_entry.is_none());
1604        let deleted_subgroup = Group::find_node_location(&destination_db.root, deleted_subgroup_uuid);
1605        assert!(deleted_subgroup.is_none());
1606        let deleted_group = Group::find_node_location(&destination_db.root, deleted_group_uuid);
1607        assert!(deleted_group.is_some());
1608
1609        assert!(destination_db.deleted_objects.contains_key(&deleted_entry_uuid));
1610        assert!(destination_db.deleted_objects.contains_key(&deleted_subgroup_uuid));
1611        assert!(!destination_db.deleted_objects.contains_key(&deleted_group_uuid));
1612    }
1613
1614    #[test]
1615    fn test_deleted_group_in_source_modified_in_destination() {
1616        let mut destination_db = create_test_database();
1617        let mut source_db = destination_db.clone();
1618
1619        let deleted_group_uuid = Uuid::new_v4();
1620
1621        thread::sleep(time::Duration::from_secs(1));
1622        source_db.deleted_objects.insert(deleted_group_uuid, Some(Times::now()));
1623
1624        thread::sleep(time::Duration::from_secs(1));
1625        let mut deleted_group = Group::new("deleted_group");
1626        deleted_group.uuid = deleted_group_uuid;
1627        group_add_child(&destination_db.root, rc_refcell_node(deleted_group), 0).unwrap();
1628
1629        let entry_count_before = get_all_entries(&destination_db.root).len();
1630        let group_count_before = get_all_groups(&destination_db.root).len();
1631
1632        let merge_result = destination_db.merge(&source_db).unwrap();
1633        assert_eq!(merge_result.warnings.len(), 0);
1634        assert_eq!(merge_result.events.len(), 0);
1635
1636        let entry_count_after = get_all_entries(&destination_db.root).len();
1637        let group_count_after = get_all_groups(&destination_db.root).len();
1638        assert_eq!(entry_count_after, entry_count_before);
1639        assert_eq!(group_count_after, group_count_before);
1640
1641        let deleted_group = Group::find_node_location(&destination_db.root, deleted_group_uuid);
1642        assert!(deleted_group.is_some());
1643
1644        assert!(!destination_db.deleted_objects.contains_key(&deleted_group_uuid));
1645    }
1646
1647    #[test]
1648    fn test_deleted_group_has_new_entries() {
1649        let mut destination_db = create_test_database();
1650        let mut source_db = destination_db.clone();
1651
1652        let mut deleted_group = Group::new("deleted_group");
1653        let deleted_group_uuid = deleted_group.uuid;
1654
1655        let mut new_entry = Entry::default();
1656        let new_entry_uuid = new_entry.uuid;
1657        new_entry.set_field_and_commit("Title", "new_entry");
1658        deleted_group.add_child(rc_refcell_node(new_entry), 0);
1659        group_add_child(&destination_db.root, rc_refcell_node(deleted_group), 0).unwrap();
1660
1661        let entry_count_before = get_all_entries(&destination_db.root).len();
1662        let group_count_before = get_all_groups(&destination_db.root).len();
1663
1664        thread::sleep(time::Duration::from_secs(1));
1665        source_db.deleted_objects.insert(deleted_group_uuid, Some(Times::now()));
1666
1667        let merge_result = destination_db.merge(&source_db).unwrap();
1668        assert_eq!(merge_result.warnings.len(), 0);
1669        assert_eq!(merge_result.events.len(), 0);
1670
1671        let entry_count_after = get_all_entries(&destination_db.root).len();
1672        let group_count_after = get_all_groups(&destination_db.root).len();
1673        assert_eq!(entry_count_after, entry_count_before);
1674        assert_eq!(group_count_after, group_count_before);
1675
1676        let deleted_group = Group::find_node_location(&destination_db.root, deleted_group_uuid);
1677        assert!(deleted_group.is_some());
1678        let new_entry = Group::find_node_location(&destination_db.root, new_entry_uuid);
1679        assert!(new_entry.is_some());
1680
1681        assert!(!destination_db.deleted_objects.contains_key(&deleted_group_uuid));
1682        assert!(!destination_db.deleted_objects.contains_key(&new_entry_uuid));
1683    }
1684
1685    #[test]
1686    fn test_add_new_non_root_entry() {
1687        let mut destination_db = create_test_database();
1688        let source_db = destination_db.clone();
1689
1690        let entry_count_before = get_all_entries(&destination_db.root).len();
1691        let group_count_before = get_all_groups(&destination_db.root).len();
1692
1693        let source_sub_group = with_node::<Group, _, _>(&source_db.root, |group| group.groups()).unwrap()[0].clone();
1694
1695        let mut new_entry = Entry::default();
1696        let new_entry_uuid = new_entry.uuid;
1697        new_entry.set_field_and_commit("Title", "new_entry");
1698        // source_sub_group.add_child(new_entry);
1699        group_add_child(&source_sub_group, rc_refcell_node(new_entry), 0).unwrap();
1700
1701        let merge_result = destination_db.merge(&source_db).unwrap();
1702        assert_eq!(merge_result.warnings.len(), 0);
1703        assert_eq!(merge_result.events.len(), 1);
1704
1705        let entry_count_after = get_all_entries(&destination_db.root).len();
1706        let group_count_after = get_all_groups(&destination_db.root).len();
1707        assert_eq!(entry_count_after, entry_count_before + 1);
1708        assert_eq!(group_count_after, group_count_before);
1709
1710        let created_entry_location = Group::find_node_location(&destination_db.root, new_entry_uuid).unwrap();
1711        assert_eq!(created_entry_location.len(), 2);
1712    }
1713
1714    #[test]
1715    fn test_add_new_entry_new_group() {
1716        let mut destination_db = create_test_database();
1717        let source_db = destination_db.clone();
1718
1719        let group_count_before = get_all_groups(&destination_db.root).len();
1720        let entry_count_before = get_all_entries(&destination_db.root).len();
1721
1722        let mut source_group = Group::new("new_group");
1723        let mut source_sub_group = Group::new("new_subgroup");
1724
1725        let mut new_entry = Entry::default();
1726        let new_entry_uuid = new_entry.uuid;
1727        new_entry.set_field_and_commit("Title", "new_entry");
1728        source_sub_group.add_child(rc_refcell_node(new_entry), 0);
1729        source_group.add_child(rc_refcell_node(source_sub_group), 0);
1730        group_add_child(&source_db.root, rc_refcell_node(source_group), 0).unwrap();
1731
1732        let merge_result = destination_db.merge(&source_db).unwrap();
1733        assert_eq!(merge_result.warnings.len(), 0);
1734        assert_eq!(merge_result.events.len(), 3);
1735
1736        let group_count_after = get_all_groups(&destination_db.root).len();
1737        let entry_count_after = get_all_entries(&destination_db.root).len();
1738        assert_eq!(entry_count_after, entry_count_before + 1);
1739        assert_eq!(group_count_after, group_count_before + 2);
1740
1741        let created_entry_location = Group::find_node_location(&destination_db.root, new_entry_uuid).unwrap();
1742        assert_eq!(created_entry_location.len(), 3);
1743    }
1744
1745    #[test]
1746    fn test_entry_relocation_existing_group() {
1747        let mut destination_db = create_test_database();
1748        let source_db = destination_db.clone();
1749
1750        let group_count_before = get_all_groups(&destination_db.root).len();
1751        let entry_count_before = get_all_entries(&destination_db.root).len();
1752
1753        thread::sleep(time::Duration::from_secs(1));
1754        let new_location_changed_timestamp = Times::now();
1755
1756        source_db
1757            .relocate_node(
1758                Uuid::parse_str(ENTRY2_ID).unwrap(),
1759                &[Uuid::parse_str(GROUP1_ID).unwrap(), Uuid::parse_str(SUBGROUP1_ID).unwrap()],
1760                &[Uuid::parse_str(GROUP2_ID).unwrap()],
1761                new_location_changed_timestamp,
1762            )
1763            .unwrap();
1764
1765        let merge_result = destination_db.merge(&source_db).unwrap();
1766        assert_eq!(merge_result.warnings.len(), 0);
1767        assert_eq!(merge_result.events.len(), 1);
1768
1769        let group_count_after = get_all_groups(&destination_db.root).len();
1770        let entry_count_after = get_all_entries(&destination_db.root).len();
1771        assert_eq!(group_count_after, group_count_before);
1772        assert_eq!(entry_count_after, entry_count_before);
1773
1774        let moved_entry_location = Group::find_node_location(&destination_db.root, Uuid::parse_str(ENTRY2_ID).unwrap()).unwrap();
1775        assert_eq!(moved_entry_location.len(), 2);
1776        assert_eq!(&moved_entry_location[0].to_string(), ROOT_GROUP_ID);
1777        assert_eq!(&moved_entry_location[1].to_string(), GROUP2_ID);
1778
1779        let moved_entry = get_entry(&destination_db, &["group2", "entry2"]);
1780        let ts = moved_entry.borrow().get_times().get_location_changed().unwrap();
1781        assert_eq!(ts, new_location_changed_timestamp);
1782    }
1783
1784    #[test]
1785    fn test_entry_relocation_and_update() {
1786        let mut destination_db = create_test_database();
1787        let source_db = destination_db.clone();
1788
1789        let group_count_before = get_all_groups(&destination_db.root).len();
1790        let entry_count_before = get_all_entries(&destination_db.root).len();
1791
1792        let entry2 = Group::find_entry(
1793            &source_db.root,
1794            &[
1795                Uuid::parse_str(GROUP1_ID).unwrap(),
1796                Uuid::parse_str(SUBGROUP1_ID).unwrap(),
1797                Uuid::parse_str(ENTRY2_ID).unwrap(),
1798            ],
1799        )
1800        .unwrap();
1801
1802        with_node_mut::<Entry, _, _>(&entry2, |entry2| {
1803            entry2.set_field_and_commit("Title", "entry2_modified_in_source");
1804        });
1805
1806        thread::sleep(time::Duration::from_secs(1));
1807        let new_location_changed_timestamp = Times::now();
1808
1809        source_db
1810            .relocate_node(
1811                Uuid::parse_str(ENTRY2_ID).unwrap(),
1812                &[Uuid::parse_str(GROUP1_ID).unwrap(), Uuid::parse_str(SUBGROUP1_ID).unwrap()],
1813                &[Uuid::parse_str(GROUP2_ID).unwrap()],
1814                new_location_changed_timestamp,
1815            )
1816            .unwrap();
1817
1818        let entry2 = Group::find_entry(
1819            &destination_db.root,
1820            &[
1821                Uuid::parse_str(GROUP1_ID).unwrap(),
1822                Uuid::parse_str(SUBGROUP1_ID).unwrap(),
1823                Uuid::parse_str(ENTRY2_ID).unwrap(),
1824            ],
1825        )
1826        .unwrap();
1827        with_node_mut::<Entry, _, _>(&entry2, |entry2| {
1828            entry2.set_field_and_commit("Title", "entry2_modified_in_destination");
1829        });
1830        let entry_modified_timestamp = entry2.borrow().get_times().get_last_modification().unwrap();
1831
1832        let merge_result = destination_db.merge(&source_db).unwrap();
1833        assert_eq!(merge_result.warnings.len(), 0);
1834        assert_eq!(merge_result.events.len(), 2);
1835
1836        let group_count_after = get_all_groups(&destination_db.root).len();
1837        let entry_count_after = get_all_entries(&destination_db.root).len();
1838        assert_eq!(group_count_after, group_count_before);
1839        assert_eq!(entry_count_after, entry_count_before);
1840
1841        let moved_entry_location = Group::find_node_location(&destination_db.root, Uuid::parse_str(ENTRY2_ID).unwrap()).unwrap();
1842        assert_eq!(moved_entry_location.len(), 2);
1843        assert_eq!(&moved_entry_location[0].to_string(), ROOT_GROUP_ID);
1844        assert_eq!(&moved_entry_location[1].to_string(), GROUP2_ID);
1845
1846        let moved_entry = get_entry(&destination_db, &["group2", "entry2_modified_in_destination"]);
1847        let ts1 = moved_entry.borrow().get_times().get_last_modification().unwrap();
1848        assert_eq!(ts1, entry_modified_timestamp,);
1849        let ts2 = moved_entry.borrow().get_times().get_location_changed().unwrap();
1850        assert_eq!(ts2, new_location_changed_timestamp);
1851    }
1852
1853    #[test]
1854    fn test_entry_relocation_in_destination_and_update() {
1855        let mut destination_db = create_test_database();
1856        let source_db = destination_db.clone();
1857
1858        let group_count_before = get_all_groups(&destination_db.root).len();
1859        let entry_count_before = get_all_entries(&destination_db.root).len();
1860
1861        let entry2 = Group::find_entry(
1862            &source_db.root,
1863            &[
1864                Uuid::parse_str(GROUP1_ID).unwrap(),
1865                Uuid::parse_str(SUBGROUP1_ID).unwrap(),
1866                Uuid::parse_str(ENTRY2_ID).unwrap(),
1867            ],
1868        )
1869        .unwrap();
1870        with_node_mut::<Entry, _, _>(&entry2, |entry2| {
1871            entry2.set_field_and_commit("Title", "entry2_modified_in_source");
1872        });
1873        let entry_modified_timestamp = entry2.borrow().get_times().get_last_modification().unwrap();
1874
1875        thread::sleep(time::Duration::from_secs(1));
1876        let new_location_changed_timestamp = Times::now();
1877
1878        destination_db
1879            .relocate_node(
1880                Uuid::parse_str(ENTRY2_ID).unwrap(),
1881                &[Uuid::parse_str(GROUP1_ID).unwrap(), Uuid::parse_str(SUBGROUP1_ID).unwrap()],
1882                &[Uuid::parse_str(GROUP2_ID).unwrap()],
1883                new_location_changed_timestamp,
1884            )
1885            .unwrap();
1886
1887        let merge_result = destination_db.merge(&source_db).unwrap();
1888        assert_eq!(merge_result.warnings.len(), 0);
1889        assert_eq!(merge_result.events.len(), 1);
1890
1891        let group_count_after = get_all_groups(&destination_db.root).len();
1892        let entry_count_after = get_all_entries(&destination_db.root).len();
1893        assert_eq!(group_count_after, group_count_before);
1894        assert_eq!(entry_count_after, entry_count_before);
1895
1896        let moved_entry_location = Group::find_node_location(&destination_db.root, Uuid::parse_str(ENTRY2_ID).unwrap()).unwrap();
1897        assert_eq!(moved_entry_location.len(), 2);
1898        assert_eq!(&moved_entry_location[0].to_string(), ROOT_GROUP_ID);
1899        assert_eq!(&moved_entry_location[1].to_string(), GROUP2_ID);
1900
1901        let moved_entry = get_entry(&destination_db, &["group2", "entry2_modified_in_source"]);
1902        let ts1 = moved_entry.borrow().get_times().get_last_modification().unwrap();
1903        assert_eq!(ts1, entry_modified_timestamp,);
1904        let ts2 = moved_entry.borrow().get_times().get_location_changed().unwrap();
1905        assert_eq!(ts2, new_location_changed_timestamp);
1906    }
1907
1908    #[test]
1909    fn test_entry_relocation_new_group() {
1910        let mut destination_db = create_test_database();
1911
1912        let entry_count_before = get_all_entries(&destination_db.root).len();
1913        let group_count_before = get_all_groups(&destination_db.root).len();
1914
1915        let source_db = destination_db.clone();
1916        let mut new_group = Group::new("new_group");
1917        let new_group_uuid = new_group.uuid;
1918
1919        let mut new_entry = Entry::default();
1920        let entry_uuid = new_entry.uuid;
1921        new_entry.set_field_and_commit("Title", "entry1");
1922
1923        thread::sleep(time::Duration::from_secs(1));
1924        new_entry.times.set_location_changed(Some(Times::now()));
1925        // FIXME we should not have to update the history here. We should
1926        // have a better compare function in the merge function instead.
1927        new_entry.update_history();
1928        new_group.add_child(rc_refcell_node(new_entry), 0);
1929        group_add_child(&source_db.root, rc_refcell_node(new_group), 0).unwrap();
1930
1931        let merge_result = destination_db.merge(&source_db).unwrap();
1932        assert_eq!(merge_result.warnings.len(), 0);
1933        assert_eq!(merge_result.events.len(), 2);
1934
1935        let entry_count_after = get_all_entries(&destination_db.root).len();
1936        let group_count_after = get_all_groups(&destination_db.root).len();
1937        assert_eq!(entry_count_after, entry_count_before + 1);
1938        assert_eq!(group_count_after, group_count_before + 1);
1939
1940        let created_entry_location = Group::find_node_location(&destination_db.root, entry_uuid).unwrap();
1941        assert_eq!(created_entry_location.len(), 2);
1942        assert_eq!(&created_entry_location[0].to_string(), ROOT_GROUP_ID);
1943        assert_eq!(created_entry_location[1], new_group_uuid);
1944    }
1945
1946    #[test]
1947    fn test_group_relocation() {
1948        let mut destination_db = create_test_database();
1949        let source_db = destination_db.clone();
1950
1951        let entry_count_before = get_all_entries(&destination_db.root).len();
1952        let group_count_before = get_all_groups(&destination_db.root).len();
1953
1954        let source_group_1 = get_group(&source_db, &["group1"]);
1955        let source_sub_group_1 = with_node_mut::<Group, _, _>(&source_group_1, |g| g.remove_node(Uuid::parse_str(SUBGROUP1_ID).unwrap()))
1956            .unwrap()
1957            .unwrap();
1958        assert!(node_is_group(&source_sub_group_1));
1959        thread::sleep(time::Duration::from_secs(1));
1960        let new_location_changed_timestamp = Times::now();
1961        source_sub_group_1
1962            .borrow_mut()
1963            .get_times_mut()
1964            .set_location_changed(Some(new_location_changed_timestamp));
1965
1966        let source_group_2 = get_group(&source_db, &["group2"]);
1967        group_add_child(&source_group_2, source_sub_group_1, 0).unwrap();
1968
1969        let merge_result = destination_db.merge(&source_db).unwrap();
1970        assert_eq!(merge_result.warnings.len(), 0);
1971        assert_eq!(merge_result.events.len(), 1);
1972
1973        let entry_count_after = get_all_entries(&destination_db.root).len();
1974        let group_count_after = get_all_groups(&destination_db.root).len();
1975        assert_eq!(entry_count_after, entry_count_before);
1976        assert_eq!(group_count_after, group_count_before);
1977
1978        let created_entry_location = Group::find_node_location(&destination_db.root, Uuid::parse_str(ENTRY2_ID).unwrap()).unwrap();
1979        assert_eq!(created_entry_location.len(), 3);
1980        assert_eq!(created_entry_location[0], destination_db.root.borrow().get_uuid());
1981        assert_eq!(&created_entry_location[1].to_string(), GROUP2_ID);
1982        assert_eq!(&created_entry_location[2].to_string(), SUBGROUP1_ID);
1983
1984        let relocated_group = get_group(&destination_db, &["group2", "subgroup1"]);
1985        let ts = relocated_group.borrow().get_times().get_location_changed().unwrap();
1986        assert_eq!(ts, new_location_changed_timestamp);
1987    }
1988
1989    #[test]
1990    fn test_update_in_destination_no_conflict() {
1991        let mut destination_db = create_test_database();
1992        let source_db = destination_db.clone();
1993
1994        let entry_count_before = get_all_entries(&destination_db.root).len();
1995        let group_count_before = get_all_groups(&destination_db.root).len();
1996
1997        let entry = with_node::<Group, _, _>(&destination_db.root, |group| group.entries()).unwrap()[0].clone();
1998        with_node_mut::<Entry, _, _>(&entry, |entry| entry.set_field_and_commit("Title", "entry1_updated")).unwrap();
1999
2000        let merge_result = destination_db.merge(&source_db).unwrap();
2001        assert_eq!(merge_result.warnings.len(), 0);
2002        assert_eq!(merge_result.events.len(), 0);
2003
2004        let entry = with_node::<Group, _, _>(&destination_db.root, |group| group.entries()).unwrap()[0].clone();
2005        let merged_history = with_node::<Entry, _, _>(&entry, |entry| entry.history.clone()).unwrap().unwrap();
2006        assert!(merged_history.is_ordered());
2007        assert_eq!(merged_history.entries.len(), 2);
2008        let merged_entry = &merged_history.entries[1];
2009        assert_eq!(merged_entry.get_title(), Some("entry1"));
2010
2011        let entry_count_after = get_all_entries(&destination_db.root).len();
2012        let group_count_after = get_all_groups(&destination_db.root).len();
2013        assert_eq!(entry_count_after, entry_count_before);
2014        assert_eq!(group_count_after, group_count_before);
2015
2016        let entry = with_node::<Group, _, _>(&destination_db.root, |group| group.entries()).unwrap()[0].clone();
2017        assert_eq!(entry.borrow().get_title(), Some("entry1_updated"));
2018    }
2019
2020    #[test]
2021    fn test_update_in_source_no_conflict() {
2022        let mut destination_db = create_test_database();
2023        let source_db = destination_db.clone();
2024
2025        let entry_count_before = get_all_entries(&destination_db.root).len();
2026        let group_count_before = get_all_groups(&destination_db.root).len();
2027
2028        let entry = with_node::<Group, _, _>(&source_db.root, |group| group.entries()).unwrap()[0].clone();
2029        with_node_mut::<Entry, _, _>(&entry, |entry| entry.set_field_and_commit("Title", "entry1_updated")).unwrap();
2030
2031        let merge_result = destination_db.merge(&source_db).unwrap();
2032        assert_eq!(merge_result.warnings.len(), 0);
2033        assert_eq!(merge_result.events.len(), 1);
2034
2035        let entry = with_node::<Group, _, _>(&destination_db.root, |group| group.entries()).unwrap()[0].clone();
2036        let merged_history = with_node::<Entry, _, _>(&entry, |entry| entry.history.clone()).unwrap().unwrap();
2037        assert!(merged_history.is_ordered());
2038        assert_eq!(merged_history.entries.len(), 2);
2039        let merged_entry = &merged_history.entries[1];
2040        assert_eq!(merged_entry.get_title(), Some("entry1"));
2041
2042        let entry_count_after = get_all_entries(&destination_db.root).len();
2043        let group_count_after = get_all_groups(&destination_db.root).len();
2044        assert_eq!(entry_count_after, entry_count_before);
2045        assert_eq!(group_count_after, group_count_before);
2046
2047        let entry = with_node::<Group, _, _>(&destination_db.root, |group| group.entries()).unwrap()[0].clone();
2048        assert_eq!(entry.borrow().get_title(), Some("entry1_updated"));
2049    }
2050
2051    #[test]
2052    fn test_update_with_conflicts() {
2053        let mut destination_db = create_test_database();
2054        let source_db = destination_db.clone();
2055
2056        let entry_count_before = get_all_entries(&destination_db.root).len();
2057        let group_count_before = get_all_groups(&destination_db.root).len();
2058
2059        let entry = with_node::<Group, _, _>(&destination_db.root, |group| group.entries()).unwrap()[0].clone();
2060        with_node_mut::<Entry, _, _>(&entry, |e| e.set_field_and_commit("Title", "entry1_updated_from_destination")).unwrap();
2061
2062        let entry = with_node::<Group, _, _>(&source_db.root, |group| group.entries()).unwrap()[0].clone();
2063        with_node_mut::<Entry, _, _>(&entry, |entry| entry.set_field_and_commit("Title", "entry1_updated_from_source")).unwrap();
2064
2065        let merge_result = destination_db.merge(&source_db).unwrap();
2066        assert_eq!(merge_result.warnings.len(), 0);
2067        assert_eq!(merge_result.events.len(), 1);
2068
2069        let entry_count_after = get_all_entries(&destination_db.root).len();
2070        let group_count_after = get_all_groups(&destination_db.root).len();
2071        assert_eq!(entry_count_after, entry_count_before);
2072        assert_eq!(group_count_after, group_count_before);
2073
2074        let entry = with_node::<Group, _, _>(&destination_db.root, |group| group.entries()).unwrap()[0].clone();
2075        assert_eq!(entry.borrow().get_title(), Some("entry1_updated_from_source"));
2076
2077        let merged_history = with_node::<Entry, _, _>(&entry, |entry| entry.history.clone()).unwrap().unwrap();
2078        assert!(merged_history.is_ordered());
2079        assert_eq!(merged_history.entries.len(), 3);
2080        let merged_entry = &merged_history.entries[1];
2081        assert_eq!(merged_entry.get_title(), Some("entry1_updated_from_destination"));
2082
2083        // Merging again should not result in any additional change.
2084        let merge_result = destination_db.merge(&destination_db.clone()).unwrap();
2085        assert_eq!(merge_result.warnings.len(), 0);
2086        assert_eq!(merge_result.events.len(), 0);
2087    }
2088
2089    #[test]
2090    fn test_group_update_in_source() {
2091        let mut destination_db = create_test_database();
2092        let source_db = destination_db.clone();
2093
2094        let entry_count_before = get_all_entries(&destination_db.root).len();
2095        let group_count_before = get_all_groups(&destination_db.root).len();
2096
2097        let group = get_group(&source_db, &["group1", "subgroup1"]);
2098        group.borrow_mut().set_title(Some("subgroup1_updated_name"));
2099        // Making sure to wait 1 sec before update the timestamp, to make
2100        // sure that we get a different modification timestamp.
2101        thread::sleep(time::Duration::from_secs(1));
2102        let new_modification_timestamp = Times::now();
2103        group
2104            .borrow_mut()
2105            .get_times_mut()
2106            .set_last_modification(Some(new_modification_timestamp));
2107
2108        let merge_result = destination_db.merge(&source_db).unwrap();
2109        assert_eq!(merge_result.warnings.len(), 0);
2110        assert_eq!(merge_result.events.len(), 1);
2111
2112        let entry_count_after = get_all_entries(&destination_db.root).len();
2113        let group_count_after = get_all_groups(&destination_db.root).len();
2114        assert_eq!(entry_count_after, entry_count_before);
2115        assert_eq!(group_count_after, group_count_before);
2116
2117        let modified_group = get_group(&destination_db, &["group1", "subgroup1_updated_name"]);
2118        assert_eq!(modified_group.borrow().get_title().unwrap(), "subgroup1_updated_name");
2119        let ts = modified_group.borrow().get_times().get_last_modification();
2120        assert_eq!(ts, Some(new_modification_timestamp));
2121    }
2122
2123    #[test]
2124    fn test_group_update_in_destination() {
2125        let mut destination_db = create_test_database();
2126        let source_db = destination_db.clone();
2127
2128        let entry_count_before = get_all_entries(&destination_db.root).len();
2129        let group_count_before = get_all_groups(&destination_db.root).len();
2130
2131        let group = get_group(&destination_db, &["group1", "subgroup1"]);
2132        group.borrow_mut().set_title(Some("subgroup1_updated_name"));
2133        // Making sure to wait 1 sec before update the timestamp, to make
2134        // sure that we get a different modification timestamp.
2135        thread::sleep(time::Duration::from_secs(1));
2136        let new_modification_timestamp = Times::now();
2137        group
2138            .borrow_mut()
2139            .get_times_mut()
2140            .set_last_modification(Some(new_modification_timestamp));
2141
2142        let merge_result = destination_db.merge(&source_db).unwrap();
2143        assert_eq!(merge_result.warnings.len(), 0);
2144        assert_eq!(merge_result.events.len(), 0);
2145
2146        let entry_count_after = get_all_entries(&destination_db.root).len();
2147        let group_count_after = get_all_groups(&destination_db.root).len();
2148        assert_eq!(entry_count_after, entry_count_before);
2149        assert_eq!(group_count_after, group_count_before);
2150
2151        let modified_group = get_group(&destination_db, &["group1", "subgroup1_updated_name"]);
2152        assert_eq!(modified_group.borrow().get_title(), Some("subgroup1_updated_name"));
2153        assert_eq!(
2154            modified_group.borrow().get_times().get_last_modification(),
2155            Some(new_modification_timestamp),
2156        );
2157    }
2158
2159    #[test]
2160    fn test_group_update_and_relocation() {
2161        let mut destination_db = create_test_database();
2162        let source_db = destination_db.clone();
2163
2164        let entry_count_before = get_all_entries(&destination_db.root).len();
2165        let group_count_before = get_all_groups(&destination_db.root).len();
2166
2167        let group = get_group(&source_db, &["group1", "subgroup1"]);
2168        group.borrow_mut().set_title(Some("subgroup1_updated_name"));
2169        // Making sure to wait 1 sec before update the timestamp, to make
2170        // sure that we get a different modification timestamp.
2171        thread::sleep(time::Duration::from_secs(1));
2172        let new_modification_timestamp = Times::now();
2173        group
2174            .borrow_mut()
2175            .get_times_mut()
2176            .set_last_modification(Some(new_modification_timestamp));
2177
2178        source_db
2179            .relocate_node(
2180                Uuid::parse_str(SUBGROUP1_ID).unwrap(),
2181                &[Uuid::parse_str(GROUP1_ID).unwrap()],
2182                &[Uuid::parse_str(GROUP2_ID).unwrap()],
2183                new_modification_timestamp,
2184            )
2185            .unwrap();
2186
2187        let merge_result = destination_db.merge(&source_db).unwrap();
2188        assert_eq!(merge_result.warnings.len(), 0);
2189        assert_eq!(merge_result.events.len(), 2);
2190
2191        let entry_count_after = get_all_entries(&destination_db.root).len();
2192        let group_count_after = get_all_groups(&destination_db.root).len();
2193        assert_eq!(entry_count_after, entry_count_before);
2194        assert_eq!(group_count_after, group_count_before);
2195
2196        let modified_group = get_group(&destination_db, &["group2", "subgroup1_updated_name"]);
2197        assert_eq!(modified_group.borrow().get_title(), Some("subgroup1_updated_name"));
2198        assert_eq!(
2199            modified_group.borrow().get_times().get_last_modification(),
2200            Some(new_modification_timestamp),
2201        );
2202    }
2203
2204    #[test]
2205    fn test_group_update_in_destination_and_relocation_in_source() {
2206        let mut destination_db = create_test_database();
2207        let source_db = destination_db.clone();
2208
2209        let entry_count_before = get_all_entries(&destination_db.root).len();
2210        let group_count_before = get_all_groups(&destination_db.root).len();
2211
2212        let group = get_group(&source_db, &["group1", "subgroup1"]);
2213        group.borrow_mut().set_title(Some("subgroup1_updated_name"));
2214        // Making sure to wait 1 sec before update the timestamp, to make
2215        // sure that we get a different modification timestamp.
2216        thread::sleep(time::Duration::from_secs(1));
2217        let new_modification_timestamp = Times::now();
2218        group
2219            .borrow_mut()
2220            .get_times_mut()
2221            .set_last_modification(Some(new_modification_timestamp));
2222
2223        thread::sleep(time::Duration::from_secs(1));
2224        let new_location_changed_timestamp = Times::now();
2225        destination_db
2226            .relocate_node(
2227                Uuid::parse_str(SUBGROUP1_ID).unwrap(),
2228                &[Uuid::parse_str(GROUP1_ID).unwrap()],
2229                &[Uuid::parse_str(GROUP2_ID).unwrap()],
2230                new_location_changed_timestamp,
2231            )
2232            .unwrap();
2233
2234        let merge_result = destination_db.merge(&source_db).unwrap();
2235        assert_eq!(merge_result.warnings.len(), 0);
2236        assert_eq!(merge_result.events.len(), 1);
2237
2238        let entry_count_after = get_all_entries(&destination_db.root).len();
2239        let group_count_after = get_all_groups(&destination_db.root).len();
2240        assert_eq!(entry_count_after, entry_count_before);
2241        assert_eq!(group_count_after, group_count_before);
2242
2243        let modified_group = get_group(&destination_db, &["group2", "subgroup1_updated_name"]);
2244        assert_eq!(modified_group.borrow().get_title(), Some("subgroup1_updated_name"));
2245        assert_eq!(
2246            modified_group.borrow().get_times().get_last_modification(),
2247            Some(new_modification_timestamp)
2248        );
2249        assert_eq!(
2250            modified_group.borrow().get_times().get_location_changed(),
2251            Some(new_location_changed_timestamp)
2252        );
2253    }
2254
2255    #[test]
2256    fn test_icon_added_in_source() {
2257        let mut destination_db = create_test_database();
2258        let mut source_db = destination_db.clone();
2259
2260        let new_icon_id = Uuid::new_v4();
2261        source_db.meta.custom_icons.insert(
2262            new_icon_id,
2263            CustomIcon::new(new_icon_id, None, Some(Times::now()), vec![1, 2, 3, 4]),
2264        );
2265        let entry = search_node_by_uuid_with_specific_type::<Entry>(&source_db.root, Uuid::parse_str(ENTRY1_ID).unwrap()).unwrap();
2266        with_node_mut::<Entry, _, _>(&entry, |entry| {
2267            entry.custom_icon = Some(new_icon_id);
2268            entry.get_times_mut().set_last_modification(Some(Times::now()));
2269        });
2270
2271        let merge_result = destination_db.merge(&source_db).unwrap();
2272        assert_eq!(merge_result.warnings.len(), 0);
2273        assert_eq!(merge_result.events.len(), 2);
2274
2275        assert!(destination_db.meta.custom_icon(new_icon_id).is_some());
2276    }
2277
2278    #[test]
2279    fn test_icon_updated_in_source() {
2280        let mut destination_db = create_test_database();
2281
2282        let icon_id = Uuid::new_v4();
2283        destination_db
2284            .meta
2285            .custom_icons
2286            .insert(icon_id, CustomIcon::new(icon_id, None, Some(Times::epoch()), vec![1, 2, 3, 4]));
2287
2288        let mut source_db = destination_db.clone();
2289
2290        let source_icon = source_db.meta.custom_icons.get_mut(&icon_id).unwrap();
2291        source_icon.data = vec![5, 6, 7, 8];
2292        source_icon.last_modification_time = Some(Times::now());
2293
2294        let merge_result = destination_db.merge(&source_db).unwrap();
2295        assert_eq!(merge_result.warnings.len(), 0);
2296        assert_eq!(merge_result.events.len(), 1);
2297
2298        let icon = destination_db.meta.custom_icon(icon_id).unwrap();
2299        assert_eq!(icon.data, vec![5, 6, 7, 8]);
2300    }
2301
2302    #[test]
2303    fn test_icon_updated_in_destination() {
2304        let mut destination_db = create_test_database();
2305
2306        let icon_id = Uuid::new_v4();
2307        destination_db
2308            .meta
2309            .custom_icons
2310            .insert(icon_id, CustomIcon::new(icon_id, None, Some(Times::epoch()), vec![1, 2, 3, 4]));
2311
2312        let source_db = destination_db.clone();
2313
2314        let destination_icon = destination_db.meta.custom_icons.get_mut(&icon_id).unwrap();
2315        destination_icon.data = vec![5, 6, 7, 8];
2316        destination_icon.last_modification_time = Some(Times::now());
2317
2318        let merge_result = destination_db.merge(&source_db).unwrap();
2319        assert_eq!(merge_result.warnings.len(), 0);
2320        assert_eq!(merge_result.events.len(), 0);
2321
2322        let icon = destination_db.meta.custom_icon(icon_id).unwrap();
2323        assert_eq!(icon.data, vec![5, 6, 7, 8]);
2324    }
2325}