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 = other.icon;
585            group.custom_data = other.custom_data.clone();
586            // The location changed timestamp is handled separately when merging two databases.
587            let current_times = group.times.clone();
588            group.times = other.times.clone();
589            if let Some(t) = current_times.get_location_changed() {
590                group.times.set_location_changed(Some(t));
591            }
592            group.is_expanded = other.is_expanded;
593            group.default_autotype_sequence = other.default_autotype_sequence.clone();
594            group.enable_autotype = other.enable_autotype;
595            group.enable_searching = other.enable_searching;
596            group.last_top_visible_entry = other.last_top_visible_entry;
597        })
598        .unwrap();
599        log.events.push(MergeEvent {
600            event_type: MergeEventType::GroupUpdated,
601            node_uuid: group_uuid,
602        });
603        Ok(log)
604    }
605
606    pub(crate) fn _has_diverged_from(&self, other: &Group) -> bool {
607        let new_times = Times::new();
608        let mut self_purged = self.clone();
609        self_purged.times = new_times.clone();
610        self_purged.children = vec![];
611        let mut other_purged = other.clone();
612        other_purged.times = new_times.clone();
613        other_purged.children = vec![];
614        !self_purged.eq(&other_purged)
615    }
616
617    fn replace_entry(root: &NodePtr, entry: &NodePtr) -> bool {
618        let uuid = entry.borrow().get_uuid();
619        if let Some(target_entry) = search_node_by_uuid_with_specific_type::<Entry>(root, uuid) {
620            return with_node_mut::<Entry, _, _>(&target_entry, |e| e.replaced_with(entry)).unwrap_or(false);
621        }
622        false
623    }
624
625    pub(crate) fn has_group(&self, uuid: Uuid) -> bool {
626        self.children.iter().any(|n| n.borrow().get_uuid() == uuid && node_is_group(n))
627    }
628
629    fn get_or_create_group(group: &NodePtr, location: &[Uuid], create_groups: bool) -> crate::Result<NodePtr> {
630        if location.is_empty() {
631            return Err("Empty location.".into());
632        }
633
634        let mut remaining_location = location.to_owned();
635        remaining_location.remove(0);
636
637        if remaining_location.is_empty() {
638            return Ok(group.clone());
639        }
640
641        let next_location = &remaining_location[0];
642        let mut next_location_uuid = *next_location;
643
644        if !with_node::<Group, _, _>(group, |g| g.has_group(next_location_uuid)).unwrap() && create_groups {
645            let mut current_group: Option<NodePtr> = None;
646            for i in (0..(remaining_location.len())).rev() {
647                let mut new_group = Group::new(&remaining_location[i].to_string());
648                new_group.set_uuid(remaining_location[i]);
649                if let Some(current_group) = current_group {
650                    let count = group_get_children(group).map(|c| c.len()).unwrap_or(0);
651                    new_group.add_child(current_group, count);
652                }
653                current_group = Some(rc_refcell_node(new_group));
654            }
655
656            if let Some(current_group) = current_group {
657                next_location_uuid = current_group.borrow().get_uuid();
658                let count = group_get_children(group).map_or(0, |c| c.len());
659                group_add_child(group, current_group, count)?;
660            } else {
661                return Err("Could not create group.".into());
662            }
663        }
664
665        let mut target = None;
666        for node in group_get_children(group).unwrap_or_default().iter() {
667            if node_is_group(node) && node.borrow().get_uuid() == next_location_uuid {
668                target = Some(node.clone());
669                break;
670            }
671        }
672
673        match &target {
674            Some(target) => Self::get_or_create_group(target, &remaining_location, create_groups),
675            None => Err("The group was not found.".into()),
676        }
677    }
678
679    pub(crate) fn insert_entry(group: &NodePtr, entry: NodePtr, location: &[Uuid]) -> crate::Result<()> {
680        let group = Self::get_or_create_group(group, location, true)?;
681        with_node_mut::<Group, _, _>(&group, |g| {
682            let count = g.children.len();
683            g.add_child(entry, count);
684            Ok::<(), crate::Error>(())
685        })
686        .ok_or("Could not add entry")??;
687        Ok(())
688    }
689
690    pub(crate) fn remove_entry(group: &NodePtr, uuid: Uuid, location: &[Uuid]) -> crate::Result<NodePtr> {
691        let group = Self::get_or_create_group(group, location, false)?;
692
693        let mut removed_entry: Option<NodePtr> = None;
694        let mut new_nodes: Vec<NodePtr> = vec![];
695        println!(
696            "Searching for entry {} in {}",
697            uuid,
698            group.borrow().get_title().unwrap_or("No title")
699        );
700
701        with_node::<Group, _, _>(&group, |g| {
702            for node in g.children.iter() {
703                if node_is_entry(node) {
704                    let node_uuid = node.borrow().get_uuid();
705                    println!("Saw entry {node_uuid}");
706                    if node_uuid != uuid {
707                        new_nodes.push(NodePtr::from(node));
708                        continue;
709                    }
710                    removed_entry = Some(NodePtr::from(node));
711                } else if node_is_group(node) {
712                    new_nodes.push(NodePtr::from(node));
713                }
714            }
715        });
716
717        if let Some(entry) = removed_entry {
718            with_node_mut::<Group, _, _>(&group, |g| g.reset_children(new_nodes)).ok_or("Could not reset children")?;
719            Ok(entry)
720        } else {
721            let title = group.borrow().get_title().unwrap_or("No title").to_string();
722            Err(format!("Could not find entry {uuid} in group \"{title}\".").into())
723        }
724    }
725
726    pub(crate) fn find_entry_location(&self, uuid: Uuid) -> Option<Vec<Uuid>> {
727        let mut current_location = vec![self.uuid];
728        for node in &self.children {
729            if node_is_entry(node) {
730                if node.borrow().get_uuid() == uuid {
731                    return Some(current_location);
732                }
733            } else if let Some(g) = node.borrow().downcast_ref::<Group>()
734                && let Some(mut location) = g.find_entry_location(uuid)
735            {
736                current_location.append(&mut location);
737                return Some(current_location);
738            }
739        }
740        None
741    }
742
743    pub(crate) fn add_entry(parent: &NodePtr, entry: NodePtr, location: &[Uuid]) -> crate::Result<()> {
744        if location.is_empty() {
745            panic!("TODO handle this with a Response.");
746        }
747
748        let mut remaining_location = location.to_owned();
749        remaining_location.remove(0);
750
751        if remaining_location.is_empty() {
752            with_node_mut::<Group, _, _>(parent, |g| {
753                let count = g.children.len();
754                g.add_child(entry, count);
755                Ok::<(), crate::Error>(())
756            })
757            .ok_or("Could not add entry")??;
758            return Ok(());
759        }
760
761        let next_location = remaining_location[0];
762
763        println!("Searching for group {next_location:?}");
764        for node in group_get_children(parent).unwrap_or_default() {
765            if node_is_group(&node) {
766                if node.borrow().get_uuid() != next_location {
767                    continue;
768                }
769                Self::add_entry(&node, entry, &remaining_location)?;
770                return Ok(());
771            }
772        }
773
774        // The group was not found, so we create it.
775        let new_group = rc_refcell_node(Group::new(&next_location.to_string()));
776        new_group.borrow_mut().set_uuid(next_location);
777        Self::add_entry(&new_group, entry, &remaining_location)?;
778        let count = group_get_children(parent).map_or(0, |c| c.len());
779        group_add_child(parent, new_group, count)?;
780        Ok(())
781    }
782
783    /// Merge this group with another group
784    #[allow(clippy::too_many_lines)]
785    pub fn merge(root: &NodePtr, other_group: &NodePtr) -> crate::Result<MergeLog> {
786        let mut log = MergeLog::default();
787
788        let other_entries = with_node::<Group, _, _>(other_group, |g| Ok(g.get_all_entries(&[])))
789            .unwrap_or(Err(crate::Error::from("Could not downcast other group to group")))?;
790
791        // Handle entry relocation.
792        for (entry, entry_location) in &other_entries {
793            let entry_uuid = entry.borrow().get_uuid();
794            let the_entry = search_node_by_uuid_with_specific_type::<Entry>(root, entry_uuid);
795
796            let existing_entry = match the_entry {
797                Some(e) => e,
798                None => continue,
799            };
800
801            let the_entry_location = with_node::<Group, _, _>(root, |g| Ok(g.find_entry_location(entry_uuid)))
802                .unwrap_or(Err("Could not downcast root to group"))?;
803
804            let existing_entry_location = match the_entry_location {
805                Some(l) => l,
806                None => continue,
807            };
808
809            let source_location_changed_time = if let Some(t) = entry.borrow().get_times().get_location_changed() {
810                t
811            } else {
812                log.warnings
813                    .push(format!("Entry {entry_uuid} did not have a location updated timestamp"));
814                Times::epoch()
815            };
816            let destination_location_changed = if let Some(t) = existing_entry.borrow().get_times().get_location_changed() {
817                t
818            } else {
819                log.warnings
820                    .push(format!("Entry {entry_uuid} did not have a location updated timestamp"));
821                Times::now()
822            };
823            if source_location_changed_time > destination_location_changed {
824                log.events.push(MergeEvent {
825                    event_type: MergeEventType::EntryLocationUpdated,
826                    node_uuid: entry_uuid,
827                });
828                Self::remove_entry(root, entry_uuid, &existing_entry_location)?;
829                Self::insert_entry(root, entry.borrow().duplicate(), entry_location)?;
830            }
831        }
832
833        // Handle entry updates
834        for (entry, entry_location) in &other_entries {
835            let entry_uuid = entry.borrow().get_uuid();
836            let the_entry = search_node_by_uuid_with_specific_type::<Entry>(root, entry_uuid);
837            if let Some(existing_entry) = the_entry {
838                if node_is_equals_to(&existing_entry, entry) {
839                    continue;
840                }
841
842                let source_last_modification = if let Some(t) = entry.borrow().get_times().get_last_modification() {
843                    t
844                } else {
845                    log.warnings
846                        .push(format!("Entry {entry_uuid} did not have a last modification timestamp"));
847                    Times::epoch()
848                };
849                let destination_last_modification = if let Some(t) = existing_entry.borrow().get_times().get_last_modification() {
850                    t
851                } else {
852                    log.warnings
853                        .push(format!("Entry {entry_uuid} did not have a last modification timestamp"));
854                    Times::now()
855                };
856
857                if destination_last_modification == source_last_modification {
858                    if !node_is_equals_to(&existing_entry, entry) {
859                        // This should never happen.
860                        // This means that an entry was updated without updating the last modification
861                        // timestamp.
862                        return Err("Entries have the same modification time but are not the same!".into());
863                    }
864                    continue;
865                }
866
867                let (merged_entry, entry_merge_log) = if destination_last_modification > source_last_modification {
868                    Entry::merge(&existing_entry, entry)?
869                } else {
870                    Entry::merge(entry, &existing_entry)?
871                };
872                let Some(merged_entry) = merged_entry else {
873                    continue;
874                };
875                // merged_entry.borrow_mut().set_parent(existing_entry.borrow().get_parent());
876                if node_is_equals_to(&existing_entry, &merged_entry) {
877                    continue;
878                }
879
880                Group::replace_entry(root, &merged_entry);
881
882                log.events.push(MergeEvent {
883                    event_type: MergeEventType::EntryUpdated,
884                    node_uuid: merged_entry.borrow().get_uuid(),
885                });
886                log = log.merge_with(&entry_merge_log);
887            } else {
888                Self::add_entry(root, entry.borrow().duplicate(), entry_location)?;
889                // TODO should we update the time info for the entry?
890                log.events.push(MergeEvent {
891                    event_type: MergeEventType::EntryCreated,
892                    node_uuid: entry.borrow().get_uuid(),
893                });
894            }
895        }
896
897        // TODO handle deleted objects
898        Ok(log)
899    }
900
901    // Recursively get all the entries in the group, along with their
902    // location.
903    pub(crate) fn get_all_entries(&self, current_location: &[Uuid]) -> Vec<(NodePtr, Vec<Uuid>)> {
904        let mut response: Vec<(NodePtr, Vec<Uuid>)> = vec![];
905        let mut new_location = current_location.to_owned();
906        new_location.push(self.uuid);
907
908        for node in &self.children {
909            if node_is_entry(node) {
910                response.push((node.into(), new_location.clone()));
911            }
912            with_node::<Group, _, _>(node, |g| {
913                let mut new_entries = g.get_all_entries(&new_location);
914                response.append(&mut new_entries);
915            });
916        }
917        response
918    }
919}
920
921#[cfg(test)]
922pub fn entry_set_field_and_commit(entry: &NodePtr, field_name: &str, field_value: &str) -> crate::Result<()> {
923    with_node_mut::<Entry, _, _>(entry, |entry| {
924        entry.set_field_and_commit(field_name, field_value);
925        Ok(())
926    })
927    .unwrap_or(Err("node is not an Entry.".to_string()))?;
928    Ok(())
929}
930
931impl Entry {
932    pub(crate) fn merge(entry: &NodePtr, other: &NodePtr) -> Result<(Option<NodePtr>, MergeLog), MergeError> {
933        let mut log = MergeLog::default();
934        let source_last_modification = match with_node::<Entry, _, _>(other, |e| e.get_times().get_last_modification()).unwrap() {
935            Some(t) => t,
936            None => {
937                let info = format!("Entry {} did not have a last modification timestamp", other.borrow().get_uuid());
938                log.warnings.push(info);
939                Times::epoch()
940            }
941        };
942
943        let destination_last_modification = match with_node::<Entry, _, _>(entry, |e| e.get_times().get_last_modification()).unwrap() {
944            Some(t) => t,
945            None => {
946                let info = format!("Entry {} did not have a last modification timestamp", entry.borrow().get_uuid());
947                log.warnings.push(info);
948                Times::epoch()
949            }
950        };
951
952        if destination_last_modification == source_last_modification {
953            if !crate::db::merge::has_diverged_from(entry, other) {
954                // This should never happen. This means that an entry was updated without updating the last modification timestamp.
955                return Err(MergeError::EntryModificationTimeNotUpdated(other.borrow().get_uuid().to_string()));
956            }
957            return Ok((None, log));
958        }
959        let (mut merged_entry, entry_merge_log) = with_node::<Entry, _, _>(entry, |entry| {
960            with_node::<Entry, _, _>(other, |other| {
961                if destination_last_modification > source_last_modification {
962                    entry.merge_history(other)
963                } else {
964                    other.merge_history(entry)
965                }
966            })
967            .unwrap()
968        })
969        .unwrap()?;
970
971        if let location_changed_timestamp @ Some(_) = entry.borrow().get_times().get_location_changed() {
972            merged_entry.get_times_mut().set_location_changed(location_changed_timestamp);
973        }
974        Ok((Some(rc_refcell_node(merged_entry)), entry_merge_log))
975    }
976
977    pub(crate) fn merge_history(&self, other: &Entry) -> Result<(Entry, MergeLog), MergeError> {
978        let mut log = MergeLog::default();
979        let mut source_history = match &other.history {
980            Some(h) => h.clone(),
981            None => {
982                log.warnings
983                    .push(format!("Entry {} from source database had no history.", self.uuid));
984                History::default()
985            }
986        };
987        let mut destination_history = match &self.history {
988            Some(h) => h.clone(),
989            None => {
990                log.warnings
991                    .push(format!("Entry {} from destination database had no history.", self.uuid));
992                History::default()
993            }
994        };
995        let mut response = self.clone();
996        if other.has_uncommited_changes() {
997            log.warnings
998                .push(format!("Entry {} from source database has uncommitted changes.", self.uuid));
999            source_history.add_entry(other.clone());
1000        }
1001        // TODO we should probably check for uncommitted changes in the destination
1002        // database here too for consistency.
1003        let history_merge_log = destination_history.merge_with(&source_history)?;
1004        response.history = Some(destination_history);
1005        Ok((response, log.merge_with(&history_merge_log)))
1006    }
1007
1008    // Convenience function used in when merging two entries
1009    pub(crate) fn _has_diverged_from(&self, other_entry: &Entry) -> bool {
1010        let new_times = Times::default();
1011
1012        let mut self_without_times = self.clone();
1013        self_without_times.times = new_times.clone();
1014
1015        let mut other_without_times = other_entry.clone();
1016        other_without_times.times = new_times;
1017        !self_without_times.eq(&other_without_times)
1018    }
1019
1020    /*
1021    pub(crate) fn merge_(entry: &NodePtr, other: &NodePtr) -> Result<(NodePtr, MergeLog), String> {
1022        let mut log = MergeLog::default();
1023
1024        let mut source_history = match &other.borrow().as_any().downcast_ref::<Entry>().ok_or("Error")?.history {
1025            Some(h) => h.clone(),
1026            None => {
1027                log.warnings.push(format!("Entry {} had no history.", entry.borrow().get_uuid()));
1028                History::default()
1029            }
1030        };
1031        let _destination_history = with_node::<Entry, _, _>(entry, |entry| entry.history.clone()).unwrap_or_default();
1032        let mut destination_history = _destination_history.unwrap_or_else(|| {
1033            log.warnings.push(format!("Entry {} had no history.", entry.borrow().get_uuid()));
1034            History::default()
1035        });
1036
1037        let other = other.borrow().duplicate();
1038        source_history.add_entry(other.borrow().as_any().downcast_ref::<Entry>().ok_or("Error")?.clone());
1039        let history_merge_log = destination_history.merge_with(&source_history)?;
1040        let response = entry.borrow().duplicate();
1041        with_node_mut::<Entry, _, _>(&response, |entry| {
1042            entry.history = Some(destination_history);
1043        });
1044
1045        Ok((response, log.merge_with(&history_merge_log)))
1046    }
1047    */
1048
1049    // Convenience function used in unit tests, to make sure that:
1050    // 1. The history gets updated after changing a field
1051    // 2. We wait a second before commiting the changes so that the timestamp is not the same
1052    //    as it previously was. This is necessary since the timestamps in the KDBX format
1053    //    do not preserve the msecs.
1054    #[cfg(test)]
1055    pub(crate) fn set_field_and_commit(&mut self, field_name: &str, field_value: &str) {
1056        self.set_unprotected_field_pair(field_name, Some(field_value));
1057        std::thread::sleep(std::time::Duration::from_secs(1));
1058        self.update_history();
1059    }
1060
1061    pub(crate) fn replaced_with(&mut self, other: &NodePtr) -> bool {
1062        let mut success = false;
1063        with_node::<Entry, _, _>(other, |other| {
1064            self.uuid = other.uuid;
1065            self.fields = other.fields.clone();
1066            self.autotype = other.autotype.clone();
1067            self.tags = other.tags.clone();
1068            self.times = other.times.clone();
1069            self.custom_data = other.custom_data.clone();
1070            self.icon = other.icon;
1071            self.foreground_color = other.foreground_color;
1072            self.background_color = other.background_color;
1073            self.override_url = other.override_url.clone();
1074            self.quality_check = other.quality_check;
1075            self.history = other.history.clone();
1076            // self.parent = other.parent;
1077            success = true;
1078        });
1079        success
1080    }
1081}
1082
1083impl History {
1084    // Determines if the entries of the history are
1085    // ordered by last modification time.
1086    #[cfg(test)]
1087    pub(crate) fn is_ordered(&self) -> bool {
1088        let mut last_modification_time: Option<chrono::NaiveDateTime> = None;
1089        for entry in &self.entries {
1090            if last_modification_time.is_none() {
1091                last_modification_time = entry.times.get_last_modification();
1092            }
1093
1094            let entry_modification_time = entry.times.get_last_modification().unwrap();
1095            // FIXME should we also handle equal modification times??
1096            if last_modification_time.unwrap() < entry_modification_time {
1097                return false;
1098            }
1099            last_modification_time = Some(entry_modification_time);
1100        }
1101        true
1102    }
1103
1104    // Merge both histories together.
1105    pub(crate) fn merge_with(&mut self, other: &History) -> Result<MergeLog, MergeError> {
1106        let mut log = MergeLog::default();
1107        let mut new_history_entries: HashMap<chrono::NaiveDateTime, Entry> = HashMap::new();
1108
1109        for history_entry in &self.entries {
1110            let modification_time = history_entry.times.get_last_modification().unwrap();
1111            if new_history_entries.contains_key(&modification_time) {
1112                return Err(MergeError::DuplicateHistoryEntries(
1113                    modification_time.to_string(),
1114                    history_entry.uuid.to_string(),
1115                ));
1116            }
1117            new_history_entries.insert(modification_time, history_entry.clone());
1118        }
1119
1120        for history_entry in &other.entries {
1121            let modification_time = history_entry.times.get_last_modification().unwrap();
1122            let existing_history_entry = new_history_entries.get(&modification_time);
1123            if let Some(existing_history_entry) = existing_history_entry {
1124                if existing_history_entry._has_diverged_from(history_entry) {
1125                    log.warnings.push(format!(
1126                        "History entries for {} have the same modification timestamp but were not the same.",
1127                        existing_history_entry.uuid
1128                    ));
1129                }
1130            } else {
1131                new_history_entries.insert(modification_time, history_entry.clone());
1132            }
1133        }
1134
1135        let mut all_modification_times: Vec<&chrono::NaiveDateTime> = new_history_entries.keys().collect();
1136        all_modification_times.sort();
1137        all_modification_times.reverse();
1138        let mut new_entries: Vec<Entry> = vec![];
1139        for modification_time in &all_modification_times {
1140            new_entries.push(new_history_entries.get(modification_time).unwrap().clone());
1141        }
1142        self.entries = new_entries;
1143        Ok(log)
1144    }
1145}
1146
1147#[cfg(test)]
1148mod merge_tests {
1149    use std::{thread, time};
1150    use uuid::Uuid;
1151
1152    use crate::db::{
1153        CustomIcon, Database, Entry, Group, Icon, Node, NodePtr, Times, group_add_child, group_get_children, node_is_group,
1154        rc_refcell_node, search_node_by_uuid_with_specific_type, with_node, with_node_mut,
1155    };
1156
1157    fn get_entry(db: &Database, path: &[&str]) -> NodePtr {
1158        Group::get(&db.root, path).unwrap()
1159    }
1160
1161    fn get_group(db: &Database, path: &[&str]) -> NodePtr {
1162        Group::get(&db.root, path).unwrap()
1163    }
1164
1165    fn get_all_groups(group: &NodePtr) -> Vec<NodePtr> {
1166        let mut response: Vec<NodePtr> = vec![];
1167        for node in group_get_children(group).unwrap() {
1168            if node_is_group(&node) {
1169                let mut new_groups = get_all_groups(&node);
1170                response.append(&mut new_groups);
1171                response.push(node);
1172            }
1173        }
1174
1175        response
1176    }
1177
1178    fn get_all_entries(group: &NodePtr) -> Vec<NodePtr> {
1179        let mut response: Vec<NodePtr> = vec![];
1180        for node in group_get_children(group).unwrap() {
1181            if node_is_group(&node) {
1182                let mut new_entries = get_all_entries(&node);
1183                response.append(&mut new_entries);
1184            } else {
1185                response.push(node);
1186            }
1187        }
1188        response
1189    }
1190
1191    const ROOT_GROUP_ID: &str = "00000000-0000-0000-0000-000000000001";
1192    const GROUP1_ID: &str = "00000000-0000-0000-0000-000000000002";
1193    const GROUP2_ID: &str = "00000000-0000-0000-0000-000000000003";
1194    const SUBGROUP1_ID: &str = "00000000-0000-0000-0000-000000000004";
1195    const SUBGROUP2_ID: &str = "00000000-0000-0000-0000-000000000005";
1196
1197    const ENTRY1_ID: &str = "00000000-0000-0000-0000-000000000006";
1198    const ENTRY2_ID: &str = "00000000-0000-0000-0000-000000000007";
1199
1200    fn create_test_database() -> Database {
1201        let mut db = Database::new(Default::default());
1202        let mut root_group = Group::new("root");
1203        root_group.uuid = Uuid::parse_str(ROOT_GROUP_ID).unwrap();
1204
1205        let mut group1 = Group::new("group1");
1206        group1.uuid = Uuid::parse_str(GROUP1_ID).unwrap();
1207        let mut group2 = Group::new("group2");
1208        group2.uuid = Uuid::parse_str(GROUP2_ID).unwrap();
1209
1210        let mut subgroup1 = Group::new("subgroup1");
1211        subgroup1.uuid = Uuid::parse_str(SUBGROUP1_ID).unwrap();
1212        let mut subgroup2 = Group::new("subgroup2");
1213        subgroup2.uuid = Uuid::parse_str(SUBGROUP2_ID).unwrap();
1214
1215        // Placing the first entry in the root group
1216        let mut entry1 = Entry::default();
1217        entry1.set_uuid(Uuid::parse_str(ENTRY1_ID).unwrap());
1218        entry1.set_field_and_commit("Title", "entry1");
1219        root_group.add_child(rc_refcell_node(entry1), 0);
1220
1221        // Placing the second entry in a subgroup
1222        let mut entry2 = Entry::default();
1223        entry2.set_uuid(Uuid::parse_str(ENTRY2_ID).unwrap());
1224        entry2.set_field_and_commit("Title", "entry2");
1225        subgroup1.add_child(rc_refcell_node(entry2), 0);
1226
1227        group1.add_child(rc_refcell_node(subgroup1), 0);
1228        group2.add_child(rc_refcell_node(subgroup2), 0);
1229
1230        root_group.add_child(rc_refcell_node(group1), 1);
1231        root_group.add_child(rc_refcell_node(group2), 2);
1232
1233        db.root = rc_refcell_node(root_group).into();
1234        db
1235    }
1236
1237    #[test]
1238    fn test_idempotence() {
1239        let mut destination_db = create_test_database();
1240        let source_db = destination_db.clone();
1241
1242        let entry_count_before = get_all_entries(&destination_db.root).len();
1243        let group_count_before = get_all_groups(&destination_db.root).len();
1244
1245        let merge_result = destination_db.merge(&source_db).unwrap();
1246        assert_eq!(merge_result.warnings.len(), 0);
1247        assert_eq!(merge_result.events.len(), 0);
1248        assert_eq!(group_get_children(&destination_db.root).unwrap().len(), 3);
1249
1250        // The 2 groups should be exactly the same after merging, since
1251        // nothing was performed during the merge.
1252        assert_eq!(destination_db, source_db);
1253
1254        let entry_count_after = get_all_entries(&destination_db.root).len();
1255        let group_count_after = get_all_groups(&destination_db.root).len();
1256        assert_eq!(entry_count_after, entry_count_before);
1257        assert_eq!(group_count_after, group_count_before);
1258
1259        let entry = get_all_entries(&destination_db.root)[0].clone();
1260        with_node_mut::<Entry, _, _>(&entry, |entry| {
1261            entry.set_field_and_commit("Title", "entry1_updated");
1262        });
1263
1264        let merge_result = destination_db.merge(&source_db).unwrap();
1265        assert_eq!(merge_result.warnings.len(), 0);
1266        assert_eq!(merge_result.events.len(), 0);
1267        let destination_db_just_after_merge = destination_db.clone();
1268
1269        let merge_result = destination_db.merge(&source_db).unwrap();
1270        assert_eq!(merge_result.warnings.len(), 0);
1271        assert_eq!(merge_result.events.len(), 0);
1272        // Merging twice in a row, even if the first merge updated the destination group,
1273        // should not create more changes.
1274        assert_eq!(destination_db_just_after_merge, destination_db);
1275    }
1276
1277    #[test]
1278    fn test_add_new_entry() {
1279        let mut destination_db = create_test_database();
1280        let source_db = destination_db.clone();
1281
1282        let entry_count_before = get_all_entries(&destination_db.root).len();
1283        let group_count_before = get_all_groups(&destination_db.root).len();
1284
1285        let mut new_entry = Entry::default();
1286        new_entry.set_field_and_commit("Title", "new_entry");
1287        group_add_child(&source_db.root, rc_refcell_node(new_entry), 0).unwrap();
1288
1289        let merge_result = destination_db.merge(&source_db).unwrap();
1290        assert_eq!(merge_result.warnings.len(), 0);
1291        assert_eq!(merge_result.events.len(), 1);
1292
1293        let entry_count_after = get_all_entries(&destination_db.root).len();
1294        let group_count_after = get_all_groups(&destination_db.root).len();
1295        assert_eq!(entry_count_after, entry_count_before + 1);
1296        assert_eq!(group_count_after, group_count_before);
1297
1298        let root_entries = with_node::<Group, _, _>(&destination_db.root, |group| group.entries()).unwrap();
1299        assert_eq!(root_entries.len(), 2);
1300
1301        let new_entry = get_entry(&destination_db, &["new_entry"]);
1302        assert_eq!(new_entry.borrow().get_title().unwrap(), "new_entry".to_string());
1303
1304        // Merging the same group again should not create a duplicate entry.
1305        let merge_result = destination_db.merge(&source_db).unwrap();
1306        assert_eq!(merge_result.warnings.len(), 0);
1307        assert_eq!(merge_result.events.len(), 0);
1308
1309        let entry_count_after = get_all_entries(&destination_db.root).len();
1310        let group_count_after = get_all_groups(&destination_db.root).len();
1311        assert_eq!(entry_count_after, entry_count_before + 1);
1312        assert_eq!(group_count_after, group_count_before);
1313    }
1314
1315    #[test]
1316    fn test_deleted_entry_in_destination() {
1317        let mut destination_db = create_test_database();
1318        let source_db = destination_db.clone();
1319
1320        let entry_count_before = get_all_entries(&destination_db.root).len();
1321        let group_count_before = get_all_groups(&destination_db.root).len();
1322
1323        let mut deleted_entry = Entry::default();
1324        let deleted_entry_uuid = deleted_entry.uuid;
1325        deleted_entry.set_field_and_commit("Title", "deleted_entry");
1326        group_add_child(&source_db.root, rc_refcell_node(deleted_entry), 0).unwrap();
1327
1328        destination_db.deleted_objects.insert(deleted_entry_uuid, Some(Times::now()));
1329
1330        let merge_result = destination_db.merge(&source_db).unwrap();
1331        assert_eq!(merge_result.warnings.len(), 0);
1332        assert_eq!(merge_result.events.len(), 0);
1333
1334        let entry_count_after = get_all_entries(&destination_db.root).len();
1335        let group_count_after = get_all_groups(&destination_db.root).len();
1336        assert_eq!(entry_count_after, entry_count_before);
1337        assert_eq!(group_count_after, group_count_before);
1338
1339        let new_entry = Group::find_node_location(&destination_db.root, deleted_entry_uuid);
1340        assert!(new_entry.is_none());
1341    }
1342
1343    #[test]
1344    fn test_updated_entry_under_deleted_group() {
1345        let mut destination_db = create_test_database();
1346        let source_db = destination_db.clone();
1347
1348        let mut modified_entry = Entry::default();
1349        modified_entry.set_field_and_commit("Title", "original_title");
1350        group_add_child(&destination_db.root, modified_entry.duplicate(), 0).unwrap();
1351
1352        let mut deleted_group = Group::new("deleted_group");
1353        let deleted_group_uuid = deleted_group.uuid;
1354        let modified_entry_uuid = modified_entry.uuid;
1355        modified_entry.set_field_and_commit("Title", "modified_title");
1356        deleted_group.add_child(rc_refcell_node(modified_entry), 0);
1357        group_add_child(&source_db.root, rc_refcell_node(deleted_group), 0).unwrap();
1358
1359        let entry_count_before = get_all_entries(&destination_db.root).len();
1360        let group_count_before = get_all_groups(&destination_db.root).len();
1361
1362        destination_db.deleted_objects.insert(deleted_group_uuid, Some(Times::now()));
1363
1364        let merge_result = destination_db.merge(&source_db).unwrap();
1365        assert_eq!(merge_result.warnings.len(), 0);
1366        assert_eq!(merge_result.events.len(), 1);
1367
1368        let entry_count_after = get_all_entries(&destination_db.root).len();
1369        let group_count_after = get_all_groups(&destination_db.root).len();
1370        assert_eq!(entry_count_after, entry_count_before);
1371        assert_eq!(group_count_after, group_count_before);
1372
1373        let deleted_group = Group::find_node_location(&destination_db.root, deleted_group_uuid);
1374        assert!(deleted_group.is_none());
1375
1376        let modified_entry_location = Group::find_node_location(&destination_db.root, modified_entry_uuid);
1377        assert!(modified_entry_location.is_some());
1378
1379        let modified_entry = Group::find_entry(&destination_db.root, &[modified_entry_uuid]).unwrap();
1380        assert_eq!(modified_entry.borrow().get_title(), Some("modified_title"));
1381    }
1382
1383    #[test]
1384    fn test_deleted_group_in_destination() {
1385        let mut destination_db = create_test_database();
1386        let source_db = destination_db.clone();
1387
1388        let entry_count_before = get_all_entries(&destination_db.root).len();
1389        let group_count_before = get_all_groups(&destination_db.root).len();
1390
1391        let deleted_group = Group::new("deleted_group");
1392        let deleted_group_uuid = deleted_group.uuid;
1393        group_add_child(&source_db.root, rc_refcell_node(deleted_group), 0).unwrap();
1394
1395        destination_db.deleted_objects.insert(deleted_group_uuid, Some(Times::now()));
1396
1397        let merge_result = destination_db.merge(&source_db).unwrap();
1398        assert_eq!(merge_result.warnings.len(), 0);
1399        assert_eq!(merge_result.events.len(), 0);
1400
1401        let entry_count_after = get_all_entries(&destination_db.root).len();
1402        let group_count_after = get_all_groups(&destination_db.root).len();
1403        assert_eq!(entry_count_after, entry_count_before);
1404        assert_eq!(group_count_after, group_count_before);
1405
1406        let deleted_group = Group::find_node_location(&destination_db.root, deleted_group_uuid);
1407        assert!(deleted_group.is_none());
1408    }
1409
1410    #[test]
1411    fn test_deleted_entry_in_source() {
1412        let mut destination_db = create_test_database();
1413        let mut source_db = destination_db.clone();
1414
1415        let mut deleted_entry = Entry::default();
1416        let deleted_entry_uuid = deleted_entry.uuid;
1417        deleted_entry.set_field_and_commit("Title", "deleted_entry");
1418        group_add_child(&destination_db.root, rc_refcell_node(deleted_entry), 0).unwrap();
1419
1420        let entry_count_before = get_all_entries(&destination_db.root).len();
1421        let group_count_before = get_all_groups(&destination_db.root).len();
1422
1423        thread::sleep(time::Duration::from_secs(1));
1424        source_db.deleted_objects.insert(deleted_entry_uuid, Some(Times::now()));
1425
1426        let merge_result = destination_db.merge(&source_db).unwrap();
1427        assert_eq!(merge_result.warnings.len(), 0);
1428        assert_eq!(merge_result.events.len(), 1);
1429
1430        let entry_count_after = get_all_entries(&destination_db.root).len();
1431        let group_count_after = get_all_groups(&destination_db.root).len();
1432        assert_eq!(entry_count_after, entry_count_before - 1);
1433        assert_eq!(group_count_after, group_count_before);
1434
1435        let new_entry = Group::find_node_location(&destination_db.root, deleted_entry_uuid);
1436        assert!(new_entry.is_none());
1437
1438        assert!(destination_db.deleted_objects.contains_key(&deleted_entry_uuid));
1439    }
1440
1441    #[test]
1442    fn test_deleted_group_in_source() {
1443        let mut destination_db = create_test_database();
1444        let mut source_db = destination_db.clone();
1445
1446        let deleted_group = Group::new("deleted_group");
1447        let deleted_group_uuid = deleted_group.uuid;
1448        group_add_child(&destination_db.root, rc_refcell_node(deleted_group), 0).unwrap();
1449
1450        let entry_count_before = get_all_entries(&destination_db.root).len();
1451        let group_count_before = get_all_groups(&destination_db.root).len();
1452
1453        thread::sleep(time::Duration::from_secs(1));
1454        source_db.deleted_objects.insert(deleted_group_uuid, Some(Times::now()));
1455
1456        let merge_result = destination_db.merge(&source_db).unwrap();
1457        assert_eq!(merge_result.warnings.len(), 0);
1458        assert_eq!(merge_result.events.len(), 1);
1459
1460        let entry_count_after = get_all_entries(&destination_db.root).len();
1461        let group_count_after = get_all_groups(&destination_db.root).len();
1462        assert_eq!(entry_count_after, entry_count_before);
1463        assert_eq!(group_count_after, group_count_before - 1);
1464
1465        let deleted_group = Group::find_node_location(&destination_db.root, deleted_group_uuid);
1466        assert!(deleted_group.is_none());
1467
1468        assert!(destination_db.deleted_objects.contains_key(&deleted_group_uuid));
1469    }
1470
1471    #[test]
1472    fn test_deleted_entry_in_source_modified_in_destination() {
1473        let mut destination_db = create_test_database();
1474        let mut source_db = destination_db.clone();
1475
1476        let deleted_entry_uuid = Uuid::new_v4();
1477
1478        thread::sleep(time::Duration::from_secs(1));
1479        source_db.deleted_objects.insert(deleted_entry_uuid, Some(Times::now()));
1480
1481        thread::sleep(time::Duration::from_secs(1));
1482        let mut deleted_entry = Entry::default();
1483        deleted_entry.set_uuid(deleted_entry_uuid);
1484        deleted_entry.set_field_and_commit("Title", "deleted_entry");
1485        group_add_child(&destination_db.root, rc_refcell_node(deleted_entry), 0).unwrap();
1486
1487        let entry_count_before = get_all_entries(&destination_db.root).len();
1488        let group_count_before = get_all_groups(&destination_db.root).len();
1489
1490        let merge_result = destination_db.merge(&source_db).unwrap();
1491        assert_eq!(merge_result.warnings.len(), 0);
1492        assert_eq!(merge_result.events.len(), 0);
1493
1494        let entry_count_after = get_all_entries(&destination_db.root).len();
1495        let group_count_after = get_all_groups(&destination_db.root).len();
1496        assert_eq!(entry_count_after, entry_count_before);
1497        assert_eq!(group_count_after, group_count_before);
1498
1499        let new_entry = Group::find_node_location(&destination_db.root, deleted_entry_uuid);
1500        assert!(new_entry.is_some());
1501
1502        assert!(!destination_db.deleted_objects.contains_key(&deleted_entry_uuid));
1503    }
1504
1505    #[test]
1506    fn test_group_subtree_deletion() {
1507        let mut destination_db = create_test_database();
1508        let mut source_db = destination_db.clone();
1509
1510        let deleted_entry_uuid = Uuid::new_v4();
1511        let deleted_group_uuid = Uuid::new_v4();
1512        let deleted_subgroup_uuid = Uuid::new_v4();
1513
1514        thread::sleep(time::Duration::from_secs(1));
1515        let mut deleted_entry = Entry::default();
1516        deleted_entry.set_uuid(deleted_entry_uuid);
1517        deleted_entry.set_field_and_commit("Title", "deleted_entry");
1518
1519        let mut deleted_subgroup = Group::new("deleted_subgroup");
1520        deleted_subgroup.uuid = deleted_subgroup_uuid;
1521        deleted_subgroup.add_child(rc_refcell_node(deleted_entry), 0);
1522
1523        let mut deleted_group = Group::new("deleted_group");
1524        deleted_group.uuid = deleted_group_uuid;
1525        deleted_group.add_child(rc_refcell_node(deleted_subgroup), 0);
1526
1527        group_add_child(&destination_db.root, rc_refcell_node(deleted_group), 0).unwrap();
1528
1529        thread::sleep(time::Duration::from_secs(1));
1530        source_db.deleted_objects.insert(deleted_entry_uuid, Some(Times::now()));
1531        source_db.deleted_objects.insert(deleted_subgroup_uuid, Some(Times::now()));
1532        source_db.deleted_objects.insert(deleted_group_uuid, Some(Times::now()));
1533
1534        let entry_count_before = get_all_entries(&destination_db.root).len();
1535        let group_count_before = get_all_groups(&destination_db.root).len();
1536
1537        let merge_result = destination_db.merge(&source_db).unwrap();
1538        assert_eq!(merge_result.warnings.len(), 0);
1539        assert_eq!(merge_result.events.len(), 3);
1540
1541        let entry_count_after = get_all_entries(&destination_db.root).len();
1542        let group_count_after = get_all_groups(&destination_db.root).len();
1543        assert_eq!(entry_count_after, entry_count_before - 1);
1544        assert_eq!(group_count_after, group_count_before - 2);
1545
1546        let deleted_entry = Group::find_node_location(&destination_db.root, deleted_entry_uuid);
1547        assert!(deleted_entry.is_none());
1548        let deleted_subgroup = Group::find_node_location(&destination_db.root, deleted_subgroup_uuid);
1549        assert!(deleted_subgroup.is_none());
1550        let deleted_group = Group::find_node_location(&destination_db.root, deleted_group_uuid);
1551        assert!(deleted_group.is_none());
1552
1553        assert!(destination_db.deleted_objects.contains_key(&deleted_entry_uuid));
1554        assert!(destination_db.deleted_objects.contains_key(&deleted_subgroup_uuid));
1555        assert!(destination_db.deleted_objects.contains_key(&deleted_group_uuid));
1556    }
1557
1558    #[test]
1559    fn test_group_subtree_partial_deletion() {
1560        let mut destination_db = create_test_database();
1561        let mut source_db = destination_db.clone();
1562
1563        let deleted_entry_uuid = Uuid::new_v4();
1564        let deleted_group_uuid = Uuid::new_v4();
1565        let deleted_subgroup_uuid = Uuid::new_v4();
1566
1567        thread::sleep(time::Duration::from_secs(1));
1568        let mut deleted_entry = Entry::default();
1569        deleted_entry.set_uuid(deleted_entry_uuid);
1570        deleted_entry.set_field_and_commit("Title", "deleted_entry");
1571
1572        let mut deleted_subgroup = Group::new("deleted_subgroup");
1573        deleted_subgroup.uuid = deleted_subgroup_uuid;
1574        deleted_subgroup.add_child(rc_refcell_node(deleted_entry), 0);
1575
1576        thread::sleep(time::Duration::from_secs(1));
1577        source_db.deleted_objects.insert(deleted_entry_uuid, Some(Times::now()));
1578        source_db.deleted_objects.insert(deleted_subgroup_uuid, Some(Times::now()));
1579        source_db.deleted_objects.insert(deleted_group_uuid, Some(Times::now()));
1580
1581        thread::sleep(time::Duration::from_secs(1));
1582        let mut deleted_group = Group::new("deleted_group");
1583        deleted_group.uuid = deleted_group_uuid;
1584        deleted_group.add_child(rc_refcell_node(deleted_subgroup), 0);
1585
1586        group_add_child(&destination_db.root, rc_refcell_node(deleted_group), 0).unwrap();
1587
1588        let entry_count_before = get_all_entries(&destination_db.root).len();
1589        let group_count_before = get_all_groups(&destination_db.root).len();
1590
1591        let merge_result = destination_db.merge(&source_db).unwrap();
1592        assert_eq!(merge_result.warnings.len(), 0);
1593        assert_eq!(merge_result.events.len(), 2);
1594
1595        let entry_count_after = get_all_entries(&destination_db.root).len();
1596        let group_count_after = get_all_groups(&destination_db.root).len();
1597        assert_eq!(entry_count_after, entry_count_before - 1);
1598        assert_eq!(group_count_after, group_count_before - 1);
1599
1600        let deleted_entry = Group::find_node_location(&destination_db.root, deleted_entry_uuid);
1601        assert!(deleted_entry.is_none());
1602        let deleted_subgroup = Group::find_node_location(&destination_db.root, deleted_subgroup_uuid);
1603        assert!(deleted_subgroup.is_none());
1604        let deleted_group = Group::find_node_location(&destination_db.root, deleted_group_uuid);
1605        assert!(deleted_group.is_some());
1606
1607        assert!(destination_db.deleted_objects.contains_key(&deleted_entry_uuid));
1608        assert!(destination_db.deleted_objects.contains_key(&deleted_subgroup_uuid));
1609        assert!(!destination_db.deleted_objects.contains_key(&deleted_group_uuid));
1610    }
1611
1612    #[test]
1613    fn test_deleted_group_in_source_modified_in_destination() {
1614        let mut destination_db = create_test_database();
1615        let mut source_db = destination_db.clone();
1616
1617        let deleted_group_uuid = Uuid::new_v4();
1618
1619        thread::sleep(time::Duration::from_secs(1));
1620        source_db.deleted_objects.insert(deleted_group_uuid, Some(Times::now()));
1621
1622        thread::sleep(time::Duration::from_secs(1));
1623        let mut deleted_group = Group::new("deleted_group");
1624        deleted_group.uuid = deleted_group_uuid;
1625        group_add_child(&destination_db.root, rc_refcell_node(deleted_group), 0).unwrap();
1626
1627        let entry_count_before = get_all_entries(&destination_db.root).len();
1628        let group_count_before = get_all_groups(&destination_db.root).len();
1629
1630        let merge_result = destination_db.merge(&source_db).unwrap();
1631        assert_eq!(merge_result.warnings.len(), 0);
1632        assert_eq!(merge_result.events.len(), 0);
1633
1634        let entry_count_after = get_all_entries(&destination_db.root).len();
1635        let group_count_after = get_all_groups(&destination_db.root).len();
1636        assert_eq!(entry_count_after, entry_count_before);
1637        assert_eq!(group_count_after, group_count_before);
1638
1639        let deleted_group = Group::find_node_location(&destination_db.root, deleted_group_uuid);
1640        assert!(deleted_group.is_some());
1641
1642        assert!(!destination_db.deleted_objects.contains_key(&deleted_group_uuid));
1643    }
1644
1645    #[test]
1646    fn test_deleted_group_has_new_entries() {
1647        let mut destination_db = create_test_database();
1648        let mut source_db = destination_db.clone();
1649
1650        let mut deleted_group = Group::new("deleted_group");
1651        let deleted_group_uuid = deleted_group.uuid;
1652
1653        let mut new_entry = Entry::default();
1654        let new_entry_uuid = new_entry.uuid;
1655        new_entry.set_field_and_commit("Title", "new_entry");
1656        deleted_group.add_child(rc_refcell_node(new_entry), 0);
1657        group_add_child(&destination_db.root, rc_refcell_node(deleted_group), 0).unwrap();
1658
1659        let entry_count_before = get_all_entries(&destination_db.root).len();
1660        let group_count_before = get_all_groups(&destination_db.root).len();
1661
1662        thread::sleep(time::Duration::from_secs(1));
1663        source_db.deleted_objects.insert(deleted_group_uuid, Some(Times::now()));
1664
1665        let merge_result = destination_db.merge(&source_db).unwrap();
1666        assert_eq!(merge_result.warnings.len(), 0);
1667        assert_eq!(merge_result.events.len(), 0);
1668
1669        let entry_count_after = get_all_entries(&destination_db.root).len();
1670        let group_count_after = get_all_groups(&destination_db.root).len();
1671        assert_eq!(entry_count_after, entry_count_before);
1672        assert_eq!(group_count_after, group_count_before);
1673
1674        let deleted_group = Group::find_node_location(&destination_db.root, deleted_group_uuid);
1675        assert!(deleted_group.is_some());
1676        let new_entry = Group::find_node_location(&destination_db.root, new_entry_uuid);
1677        assert!(new_entry.is_some());
1678
1679        assert!(!destination_db.deleted_objects.contains_key(&deleted_group_uuid));
1680        assert!(!destination_db.deleted_objects.contains_key(&new_entry_uuid));
1681    }
1682
1683    #[test]
1684    fn test_add_new_non_root_entry() {
1685        let mut destination_db = create_test_database();
1686        let source_db = destination_db.clone();
1687
1688        let entry_count_before = get_all_entries(&destination_db.root).len();
1689        let group_count_before = get_all_groups(&destination_db.root).len();
1690
1691        let source_sub_group = with_node::<Group, _, _>(&source_db.root, |group| group.groups()).unwrap()[0].clone();
1692
1693        let mut new_entry = Entry::default();
1694        let new_entry_uuid = new_entry.uuid;
1695        new_entry.set_field_and_commit("Title", "new_entry");
1696        // source_sub_group.add_child(new_entry);
1697        group_add_child(&source_sub_group, rc_refcell_node(new_entry), 0).unwrap();
1698
1699        let merge_result = destination_db.merge(&source_db).unwrap();
1700        assert_eq!(merge_result.warnings.len(), 0);
1701        assert_eq!(merge_result.events.len(), 1);
1702
1703        let entry_count_after = get_all_entries(&destination_db.root).len();
1704        let group_count_after = get_all_groups(&destination_db.root).len();
1705        assert_eq!(entry_count_after, entry_count_before + 1);
1706        assert_eq!(group_count_after, group_count_before);
1707
1708        let created_entry_location = Group::find_node_location(&destination_db.root, new_entry_uuid).unwrap();
1709        assert_eq!(created_entry_location.len(), 2);
1710    }
1711
1712    #[test]
1713    fn test_add_new_entry_new_group() {
1714        let mut destination_db = create_test_database();
1715        let source_db = destination_db.clone();
1716
1717        let group_count_before = get_all_groups(&destination_db.root).len();
1718        let entry_count_before = get_all_entries(&destination_db.root).len();
1719
1720        let mut source_group = Group::new("new_group");
1721        let mut source_sub_group = Group::new("new_subgroup");
1722
1723        let mut new_entry = Entry::default();
1724        let new_entry_uuid = new_entry.uuid;
1725        new_entry.set_field_and_commit("Title", "new_entry");
1726        source_sub_group.add_child(rc_refcell_node(new_entry), 0);
1727        source_group.add_child(rc_refcell_node(source_sub_group), 0);
1728        group_add_child(&source_db.root, rc_refcell_node(source_group), 0).unwrap();
1729
1730        let merge_result = destination_db.merge(&source_db).unwrap();
1731        assert_eq!(merge_result.warnings.len(), 0);
1732        assert_eq!(merge_result.events.len(), 3);
1733
1734        let group_count_after = get_all_groups(&destination_db.root).len();
1735        let entry_count_after = get_all_entries(&destination_db.root).len();
1736        assert_eq!(entry_count_after, entry_count_before + 1);
1737        assert_eq!(group_count_after, group_count_before + 2);
1738
1739        let created_entry_location = Group::find_node_location(&destination_db.root, new_entry_uuid).unwrap();
1740        assert_eq!(created_entry_location.len(), 3);
1741    }
1742
1743    #[test]
1744    fn test_entry_relocation_existing_group() {
1745        let mut destination_db = create_test_database();
1746        let source_db = destination_db.clone();
1747
1748        let group_count_before = get_all_groups(&destination_db.root).len();
1749        let entry_count_before = get_all_entries(&destination_db.root).len();
1750
1751        thread::sleep(time::Duration::from_secs(1));
1752        let new_location_changed_timestamp = Times::now();
1753
1754        source_db
1755            .relocate_node(
1756                Uuid::parse_str(ENTRY2_ID).unwrap(),
1757                &[Uuid::parse_str(GROUP1_ID).unwrap(), Uuid::parse_str(SUBGROUP1_ID).unwrap()],
1758                &[Uuid::parse_str(GROUP2_ID).unwrap()],
1759                new_location_changed_timestamp,
1760            )
1761            .unwrap();
1762
1763        let merge_result = destination_db.merge(&source_db).unwrap();
1764        assert_eq!(merge_result.warnings.len(), 0);
1765        assert_eq!(merge_result.events.len(), 1);
1766
1767        let group_count_after = get_all_groups(&destination_db.root).len();
1768        let entry_count_after = get_all_entries(&destination_db.root).len();
1769        assert_eq!(group_count_after, group_count_before);
1770        assert_eq!(entry_count_after, entry_count_before);
1771
1772        let moved_entry_location = Group::find_node_location(&destination_db.root, Uuid::parse_str(ENTRY2_ID).unwrap()).unwrap();
1773        assert_eq!(moved_entry_location.len(), 2);
1774        assert_eq!(&moved_entry_location[0].to_string(), ROOT_GROUP_ID);
1775        assert_eq!(&moved_entry_location[1].to_string(), GROUP2_ID);
1776
1777        let moved_entry = get_entry(&destination_db, &["group2", "entry2"]);
1778        let ts = moved_entry.borrow().get_times().get_location_changed().unwrap();
1779        assert_eq!(ts, new_location_changed_timestamp);
1780    }
1781
1782    #[test]
1783    fn test_entry_relocation_and_update() {
1784        let mut destination_db = create_test_database();
1785        let source_db = destination_db.clone();
1786
1787        let group_count_before = get_all_groups(&destination_db.root).len();
1788        let entry_count_before = get_all_entries(&destination_db.root).len();
1789
1790        let entry2 = Group::find_entry(
1791            &source_db.root,
1792            &[
1793                Uuid::parse_str(GROUP1_ID).unwrap(),
1794                Uuid::parse_str(SUBGROUP1_ID).unwrap(),
1795                Uuid::parse_str(ENTRY2_ID).unwrap(),
1796            ],
1797        )
1798        .unwrap();
1799
1800        with_node_mut::<Entry, _, _>(&entry2, |entry2| {
1801            entry2.set_field_and_commit("Title", "entry2_modified_in_source");
1802        });
1803
1804        thread::sleep(time::Duration::from_secs(1));
1805        let new_location_changed_timestamp = Times::now();
1806
1807        source_db
1808            .relocate_node(
1809                Uuid::parse_str(ENTRY2_ID).unwrap(),
1810                &[Uuid::parse_str(GROUP1_ID).unwrap(), Uuid::parse_str(SUBGROUP1_ID).unwrap()],
1811                &[Uuid::parse_str(GROUP2_ID).unwrap()],
1812                new_location_changed_timestamp,
1813            )
1814            .unwrap();
1815
1816        let entry2 = Group::find_entry(
1817            &destination_db.root,
1818            &[
1819                Uuid::parse_str(GROUP1_ID).unwrap(),
1820                Uuid::parse_str(SUBGROUP1_ID).unwrap(),
1821                Uuid::parse_str(ENTRY2_ID).unwrap(),
1822            ],
1823        )
1824        .unwrap();
1825        with_node_mut::<Entry, _, _>(&entry2, |entry2| {
1826            entry2.set_field_and_commit("Title", "entry2_modified_in_destination");
1827        });
1828        let entry_modified_timestamp = entry2.borrow().get_times().get_last_modification().unwrap();
1829
1830        let merge_result = destination_db.merge(&source_db).unwrap();
1831        assert_eq!(merge_result.warnings.len(), 0);
1832        assert_eq!(merge_result.events.len(), 2);
1833
1834        let group_count_after = get_all_groups(&destination_db.root).len();
1835        let entry_count_after = get_all_entries(&destination_db.root).len();
1836        assert_eq!(group_count_after, group_count_before);
1837        assert_eq!(entry_count_after, entry_count_before);
1838
1839        let moved_entry_location = Group::find_node_location(&destination_db.root, Uuid::parse_str(ENTRY2_ID).unwrap()).unwrap();
1840        assert_eq!(moved_entry_location.len(), 2);
1841        assert_eq!(&moved_entry_location[0].to_string(), ROOT_GROUP_ID);
1842        assert_eq!(&moved_entry_location[1].to_string(), GROUP2_ID);
1843
1844        let moved_entry = get_entry(&destination_db, &["group2", "entry2_modified_in_destination"]);
1845        let ts1 = moved_entry.borrow().get_times().get_last_modification().unwrap();
1846        assert_eq!(ts1, entry_modified_timestamp,);
1847        let ts2 = moved_entry.borrow().get_times().get_location_changed().unwrap();
1848        assert_eq!(ts2, new_location_changed_timestamp);
1849    }
1850
1851    #[test]
1852    fn test_entry_relocation_in_destination_and_update() {
1853        let mut destination_db = create_test_database();
1854        let source_db = destination_db.clone();
1855
1856        let group_count_before = get_all_groups(&destination_db.root).len();
1857        let entry_count_before = get_all_entries(&destination_db.root).len();
1858
1859        let entry2 = Group::find_entry(
1860            &source_db.root,
1861            &[
1862                Uuid::parse_str(GROUP1_ID).unwrap(),
1863                Uuid::parse_str(SUBGROUP1_ID).unwrap(),
1864                Uuid::parse_str(ENTRY2_ID).unwrap(),
1865            ],
1866        )
1867        .unwrap();
1868        with_node_mut::<Entry, _, _>(&entry2, |entry2| {
1869            entry2.set_field_and_commit("Title", "entry2_modified_in_source");
1870        });
1871        let entry_modified_timestamp = entry2.borrow().get_times().get_last_modification().unwrap();
1872
1873        thread::sleep(time::Duration::from_secs(1));
1874        let new_location_changed_timestamp = Times::now();
1875
1876        destination_db
1877            .relocate_node(
1878                Uuid::parse_str(ENTRY2_ID).unwrap(),
1879                &[Uuid::parse_str(GROUP1_ID).unwrap(), Uuid::parse_str(SUBGROUP1_ID).unwrap()],
1880                &[Uuid::parse_str(GROUP2_ID).unwrap()],
1881                new_location_changed_timestamp,
1882            )
1883            .unwrap();
1884
1885        let merge_result = destination_db.merge(&source_db).unwrap();
1886        assert_eq!(merge_result.warnings.len(), 0);
1887        assert_eq!(merge_result.events.len(), 1);
1888
1889        let group_count_after = get_all_groups(&destination_db.root).len();
1890        let entry_count_after = get_all_entries(&destination_db.root).len();
1891        assert_eq!(group_count_after, group_count_before);
1892        assert_eq!(entry_count_after, entry_count_before);
1893
1894        let moved_entry_location = Group::find_node_location(&destination_db.root, Uuid::parse_str(ENTRY2_ID).unwrap()).unwrap();
1895        assert_eq!(moved_entry_location.len(), 2);
1896        assert_eq!(&moved_entry_location[0].to_string(), ROOT_GROUP_ID);
1897        assert_eq!(&moved_entry_location[1].to_string(), GROUP2_ID);
1898
1899        let moved_entry = get_entry(&destination_db, &["group2", "entry2_modified_in_source"]);
1900        let ts1 = moved_entry.borrow().get_times().get_last_modification().unwrap();
1901        assert_eq!(ts1, entry_modified_timestamp,);
1902        let ts2 = moved_entry.borrow().get_times().get_location_changed().unwrap();
1903        assert_eq!(ts2, new_location_changed_timestamp);
1904    }
1905
1906    #[test]
1907    fn test_entry_relocation_new_group() {
1908        let mut destination_db = create_test_database();
1909
1910        let entry_count_before = get_all_entries(&destination_db.root).len();
1911        let group_count_before = get_all_groups(&destination_db.root).len();
1912
1913        let source_db = destination_db.clone();
1914        let mut new_group = Group::new("new_group");
1915        let new_group_uuid = new_group.uuid;
1916
1917        let mut new_entry = Entry::default();
1918        let entry_uuid = new_entry.uuid;
1919        new_entry.set_field_and_commit("Title", "entry1");
1920
1921        thread::sleep(time::Duration::from_secs(1));
1922        new_entry.times.set_location_changed(Some(Times::now()));
1923        // FIXME we should not have to update the history here. We should
1924        // have a better compare function in the merge function instead.
1925        new_entry.update_history();
1926        new_group.add_child(rc_refcell_node(new_entry), 0);
1927        group_add_child(&source_db.root, rc_refcell_node(new_group), 0).unwrap();
1928
1929        let merge_result = destination_db.merge(&source_db).unwrap();
1930        assert_eq!(merge_result.warnings.len(), 0);
1931        assert_eq!(merge_result.events.len(), 2);
1932
1933        let entry_count_after = get_all_entries(&destination_db.root).len();
1934        let group_count_after = get_all_groups(&destination_db.root).len();
1935        assert_eq!(entry_count_after, entry_count_before + 1);
1936        assert_eq!(group_count_after, group_count_before + 1);
1937
1938        let created_entry_location = Group::find_node_location(&destination_db.root, entry_uuid).unwrap();
1939        assert_eq!(created_entry_location.len(), 2);
1940        assert_eq!(&created_entry_location[0].to_string(), ROOT_GROUP_ID);
1941        assert_eq!(created_entry_location[1], new_group_uuid);
1942    }
1943
1944    #[test]
1945    fn test_group_relocation() {
1946        let mut destination_db = create_test_database();
1947        let source_db = destination_db.clone();
1948
1949        let entry_count_before = get_all_entries(&destination_db.root).len();
1950        let group_count_before = get_all_groups(&destination_db.root).len();
1951
1952        let source_group_1 = get_group(&source_db, &["group1"]);
1953        let source_sub_group_1 = with_node_mut::<Group, _, _>(&source_group_1, |g| g.remove_node(Uuid::parse_str(SUBGROUP1_ID).unwrap()))
1954            .unwrap()
1955            .unwrap();
1956        assert!(node_is_group(&source_sub_group_1));
1957        thread::sleep(time::Duration::from_secs(1));
1958        let new_location_changed_timestamp = Times::now();
1959        source_sub_group_1
1960            .borrow_mut()
1961            .get_times_mut()
1962            .set_location_changed(Some(new_location_changed_timestamp));
1963
1964        let source_group_2 = get_group(&source_db, &["group2"]);
1965        group_add_child(&source_group_2, source_sub_group_1, 0).unwrap();
1966
1967        let merge_result = destination_db.merge(&source_db).unwrap();
1968        assert_eq!(merge_result.warnings.len(), 0);
1969        assert_eq!(merge_result.events.len(), 1);
1970
1971        let entry_count_after = get_all_entries(&destination_db.root).len();
1972        let group_count_after = get_all_groups(&destination_db.root).len();
1973        assert_eq!(entry_count_after, entry_count_before);
1974        assert_eq!(group_count_after, group_count_before);
1975
1976        let created_entry_location = Group::find_node_location(&destination_db.root, Uuid::parse_str(ENTRY2_ID).unwrap()).unwrap();
1977        assert_eq!(created_entry_location.len(), 3);
1978        assert_eq!(created_entry_location[0], destination_db.root.borrow().get_uuid());
1979        assert_eq!(&created_entry_location[1].to_string(), GROUP2_ID);
1980        assert_eq!(&created_entry_location[2].to_string(), SUBGROUP1_ID);
1981
1982        let relocated_group = get_group(&destination_db, &["group2", "subgroup1"]);
1983        let ts = relocated_group.borrow().get_times().get_location_changed().unwrap();
1984        assert_eq!(ts, new_location_changed_timestamp);
1985    }
1986
1987    #[test]
1988    fn test_update_in_destination_no_conflict() {
1989        let mut destination_db = create_test_database();
1990        let source_db = destination_db.clone();
1991
1992        let entry_count_before = get_all_entries(&destination_db.root).len();
1993        let group_count_before = get_all_groups(&destination_db.root).len();
1994
1995        let entry = with_node::<Group, _, _>(&destination_db.root, |group| group.entries()).unwrap()[0].clone();
1996        with_node_mut::<Entry, _, _>(&entry, |entry| entry.set_field_and_commit("Title", "entry1_updated")).unwrap();
1997
1998        let merge_result = destination_db.merge(&source_db).unwrap();
1999        assert_eq!(merge_result.warnings.len(), 0);
2000        assert_eq!(merge_result.events.len(), 0);
2001
2002        let entry = with_node::<Group, _, _>(&destination_db.root, |group| group.entries()).unwrap()[0].clone();
2003        let merged_history = with_node::<Entry, _, _>(&entry, |entry| entry.history.clone()).unwrap().unwrap();
2004        assert!(merged_history.is_ordered());
2005        assert_eq!(merged_history.entries.len(), 2);
2006        let merged_entry = &merged_history.entries[1];
2007        assert_eq!(merged_entry.get_title(), Some("entry1"));
2008
2009        let entry_count_after = get_all_entries(&destination_db.root).len();
2010        let group_count_after = get_all_groups(&destination_db.root).len();
2011        assert_eq!(entry_count_after, entry_count_before);
2012        assert_eq!(group_count_after, group_count_before);
2013
2014        let entry = with_node::<Group, _, _>(&destination_db.root, |group| group.entries()).unwrap()[0].clone();
2015        assert_eq!(entry.borrow().get_title(), Some("entry1_updated"));
2016    }
2017
2018    #[test]
2019    fn test_update_in_source_no_conflict() {
2020        let mut destination_db = create_test_database();
2021        let source_db = destination_db.clone();
2022
2023        let entry_count_before = get_all_entries(&destination_db.root).len();
2024        let group_count_before = get_all_groups(&destination_db.root).len();
2025
2026        let entry = with_node::<Group, _, _>(&source_db.root, |group| group.entries()).unwrap()[0].clone();
2027        with_node_mut::<Entry, _, _>(&entry, |entry| entry.set_field_and_commit("Title", "entry1_updated")).unwrap();
2028
2029        let merge_result = destination_db.merge(&source_db).unwrap();
2030        assert_eq!(merge_result.warnings.len(), 0);
2031        assert_eq!(merge_result.events.len(), 1);
2032
2033        let entry = with_node::<Group, _, _>(&destination_db.root, |group| group.entries()).unwrap()[0].clone();
2034        let merged_history = with_node::<Entry, _, _>(&entry, |entry| entry.history.clone()).unwrap().unwrap();
2035        assert!(merged_history.is_ordered());
2036        assert_eq!(merged_history.entries.len(), 2);
2037        let merged_entry = &merged_history.entries[1];
2038        assert_eq!(merged_entry.get_title(), Some("entry1"));
2039
2040        let entry_count_after = get_all_entries(&destination_db.root).len();
2041        let group_count_after = get_all_groups(&destination_db.root).len();
2042        assert_eq!(entry_count_after, entry_count_before);
2043        assert_eq!(group_count_after, group_count_before);
2044
2045        let entry = with_node::<Group, _, _>(&destination_db.root, |group| group.entries()).unwrap()[0].clone();
2046        assert_eq!(entry.borrow().get_title(), Some("entry1_updated"));
2047    }
2048
2049    #[test]
2050    fn test_update_with_conflicts() {
2051        let mut destination_db = create_test_database();
2052        let source_db = destination_db.clone();
2053
2054        let entry_count_before = get_all_entries(&destination_db.root).len();
2055        let group_count_before = get_all_groups(&destination_db.root).len();
2056
2057        let entry = with_node::<Group, _, _>(&destination_db.root, |group| group.entries()).unwrap()[0].clone();
2058        with_node_mut::<Entry, _, _>(&entry, |e| e.set_field_and_commit("Title", "entry1_updated_from_destination")).unwrap();
2059
2060        let entry = with_node::<Group, _, _>(&source_db.root, |group| group.entries()).unwrap()[0].clone();
2061        with_node_mut::<Entry, _, _>(&entry, |entry| entry.set_field_and_commit("Title", "entry1_updated_from_source")).unwrap();
2062
2063        let merge_result = destination_db.merge(&source_db).unwrap();
2064        assert_eq!(merge_result.warnings.len(), 0);
2065        assert_eq!(merge_result.events.len(), 1);
2066
2067        let entry_count_after = get_all_entries(&destination_db.root).len();
2068        let group_count_after = get_all_groups(&destination_db.root).len();
2069        assert_eq!(entry_count_after, entry_count_before);
2070        assert_eq!(group_count_after, group_count_before);
2071
2072        let entry = with_node::<Group, _, _>(&destination_db.root, |group| group.entries()).unwrap()[0].clone();
2073        assert_eq!(entry.borrow().get_title(), Some("entry1_updated_from_source"));
2074
2075        let merged_history = with_node::<Entry, _, _>(&entry, |entry| entry.history.clone()).unwrap().unwrap();
2076        assert!(merged_history.is_ordered());
2077        assert_eq!(merged_history.entries.len(), 3);
2078        let merged_entry = &merged_history.entries[1];
2079        assert_eq!(merged_entry.get_title(), Some("entry1_updated_from_destination"));
2080
2081        // Merging again should not result in any additional change.
2082        let merge_result = destination_db.merge(&destination_db.clone()).unwrap();
2083        assert_eq!(merge_result.warnings.len(), 0);
2084        assert_eq!(merge_result.events.len(), 0);
2085    }
2086
2087    #[test]
2088    fn test_group_update_in_source() {
2089        let mut destination_db = create_test_database();
2090        let source_db = destination_db.clone();
2091
2092        let entry_count_before = get_all_entries(&destination_db.root).len();
2093        let group_count_before = get_all_groups(&destination_db.root).len();
2094
2095        let group = get_group(&source_db, &["group1", "subgroup1"]);
2096        group.borrow_mut().set_title(Some("subgroup1_updated_name"));
2097        // Making sure to wait 1 sec before update the timestamp, to make
2098        // sure that we get a different modification timestamp.
2099        thread::sleep(time::Duration::from_secs(1));
2100        let new_modification_timestamp = Times::now();
2101        group
2102            .borrow_mut()
2103            .get_times_mut()
2104            .set_last_modification(Some(new_modification_timestamp));
2105
2106        let merge_result = destination_db.merge(&source_db).unwrap();
2107        assert_eq!(merge_result.warnings.len(), 0);
2108        assert_eq!(merge_result.events.len(), 1);
2109
2110        let entry_count_after = get_all_entries(&destination_db.root).len();
2111        let group_count_after = get_all_groups(&destination_db.root).len();
2112        assert_eq!(entry_count_after, entry_count_before);
2113        assert_eq!(group_count_after, group_count_before);
2114
2115        let modified_group = get_group(&destination_db, &["group1", "subgroup1_updated_name"]);
2116        assert_eq!(modified_group.borrow().get_title().unwrap(), "subgroup1_updated_name");
2117        let ts = modified_group.borrow().get_times().get_last_modification();
2118        assert_eq!(ts, Some(new_modification_timestamp));
2119    }
2120
2121    #[test]
2122    fn test_group_update_in_destination() {
2123        let mut destination_db = create_test_database();
2124        let source_db = destination_db.clone();
2125
2126        let entry_count_before = get_all_entries(&destination_db.root).len();
2127        let group_count_before = get_all_groups(&destination_db.root).len();
2128
2129        let group = get_group(&destination_db, &["group1", "subgroup1"]);
2130        group.borrow_mut().set_title(Some("subgroup1_updated_name"));
2131        // Making sure to wait 1 sec before update the timestamp, to make
2132        // sure that we get a different modification timestamp.
2133        thread::sleep(time::Duration::from_secs(1));
2134        let new_modification_timestamp = Times::now();
2135        group
2136            .borrow_mut()
2137            .get_times_mut()
2138            .set_last_modification(Some(new_modification_timestamp));
2139
2140        let merge_result = destination_db.merge(&source_db).unwrap();
2141        assert_eq!(merge_result.warnings.len(), 0);
2142        assert_eq!(merge_result.events.len(), 0);
2143
2144        let entry_count_after = get_all_entries(&destination_db.root).len();
2145        let group_count_after = get_all_groups(&destination_db.root).len();
2146        assert_eq!(entry_count_after, entry_count_before);
2147        assert_eq!(group_count_after, group_count_before);
2148
2149        let modified_group = get_group(&destination_db, &["group1", "subgroup1_updated_name"]);
2150        assert_eq!(modified_group.borrow().get_title(), Some("subgroup1_updated_name"));
2151        assert_eq!(
2152            modified_group.borrow().get_times().get_last_modification(),
2153            Some(new_modification_timestamp),
2154        );
2155    }
2156
2157    #[test]
2158    fn test_group_update_and_relocation() {
2159        let mut destination_db = create_test_database();
2160        let source_db = destination_db.clone();
2161
2162        let entry_count_before = get_all_entries(&destination_db.root).len();
2163        let group_count_before = get_all_groups(&destination_db.root).len();
2164
2165        let group = get_group(&source_db, &["group1", "subgroup1"]);
2166        group.borrow_mut().set_title(Some("subgroup1_updated_name"));
2167        // Making sure to wait 1 sec before update the timestamp, to make
2168        // sure that we get a different modification timestamp.
2169        thread::sleep(time::Duration::from_secs(1));
2170        let new_modification_timestamp = Times::now();
2171        group
2172            .borrow_mut()
2173            .get_times_mut()
2174            .set_last_modification(Some(new_modification_timestamp));
2175
2176        source_db
2177            .relocate_node(
2178                Uuid::parse_str(SUBGROUP1_ID).unwrap(),
2179                &[Uuid::parse_str(GROUP1_ID).unwrap()],
2180                &[Uuid::parse_str(GROUP2_ID).unwrap()],
2181                new_modification_timestamp,
2182            )
2183            .unwrap();
2184
2185        let merge_result = destination_db.merge(&source_db).unwrap();
2186        assert_eq!(merge_result.warnings.len(), 0);
2187        assert_eq!(merge_result.events.len(), 2);
2188
2189        let entry_count_after = get_all_entries(&destination_db.root).len();
2190        let group_count_after = get_all_groups(&destination_db.root).len();
2191        assert_eq!(entry_count_after, entry_count_before);
2192        assert_eq!(group_count_after, group_count_before);
2193
2194        let modified_group = get_group(&destination_db, &["group2", "subgroup1_updated_name"]);
2195        assert_eq!(modified_group.borrow().get_title(), Some("subgroup1_updated_name"));
2196        assert_eq!(
2197            modified_group.borrow().get_times().get_last_modification(),
2198            Some(new_modification_timestamp),
2199        );
2200    }
2201
2202    #[test]
2203    fn test_group_update_in_destination_and_relocation_in_source() {
2204        let mut destination_db = create_test_database();
2205        let source_db = destination_db.clone();
2206
2207        let entry_count_before = get_all_entries(&destination_db.root).len();
2208        let group_count_before = get_all_groups(&destination_db.root).len();
2209
2210        let group = get_group(&source_db, &["group1", "subgroup1"]);
2211        group.borrow_mut().set_title(Some("subgroup1_updated_name"));
2212        // Making sure to wait 1 sec before update the timestamp, to make
2213        // sure that we get a different modification timestamp.
2214        thread::sleep(time::Duration::from_secs(1));
2215        let new_modification_timestamp = Times::now();
2216        group
2217            .borrow_mut()
2218            .get_times_mut()
2219            .set_last_modification(Some(new_modification_timestamp));
2220
2221        thread::sleep(time::Duration::from_secs(1));
2222        let new_location_changed_timestamp = Times::now();
2223        destination_db
2224            .relocate_node(
2225                Uuid::parse_str(SUBGROUP1_ID).unwrap(),
2226                &[Uuid::parse_str(GROUP1_ID).unwrap()],
2227                &[Uuid::parse_str(GROUP2_ID).unwrap()],
2228                new_location_changed_timestamp,
2229            )
2230            .unwrap();
2231
2232        let merge_result = destination_db.merge(&source_db).unwrap();
2233        assert_eq!(merge_result.warnings.len(), 0);
2234        assert_eq!(merge_result.events.len(), 1);
2235
2236        let entry_count_after = get_all_entries(&destination_db.root).len();
2237        let group_count_after = get_all_groups(&destination_db.root).len();
2238        assert_eq!(entry_count_after, entry_count_before);
2239        assert_eq!(group_count_after, group_count_before);
2240
2241        let modified_group = get_group(&destination_db, &["group2", "subgroup1_updated_name"]);
2242        assert_eq!(modified_group.borrow().get_title(), Some("subgroup1_updated_name"));
2243        assert_eq!(
2244            modified_group.borrow().get_times().get_last_modification(),
2245            Some(new_modification_timestamp)
2246        );
2247        assert_eq!(
2248            modified_group.borrow().get_times().get_location_changed(),
2249            Some(new_location_changed_timestamp)
2250        );
2251    }
2252
2253    #[test]
2254    fn test_icon_added_in_source() {
2255        let mut destination_db = create_test_database();
2256        let mut source_db = destination_db.clone();
2257
2258        let new_icon_id = Uuid::new_v4();
2259        source_db.meta.custom_icons.insert(
2260            new_icon_id,
2261            CustomIcon::new(new_icon_id, None, Some(Times::now()), vec![1, 2, 3, 4]),
2262        );
2263        let entry = search_node_by_uuid_with_specific_type::<Entry>(&source_db.root, Uuid::parse_str(ENTRY1_ID).unwrap()).unwrap();
2264        with_node_mut::<Entry, _, _>(&entry, |entry| {
2265            entry.icon = Icon::Custom(new_icon_id);
2266            entry.get_times_mut().set_last_modification(Some(Times::now()));
2267        });
2268
2269        let merge_result = destination_db.merge(&source_db).unwrap();
2270        assert_eq!(merge_result.warnings.len(), 0);
2271        assert_eq!(merge_result.events.len(), 2);
2272
2273        assert!(destination_db.meta.custom_icon(new_icon_id).is_some());
2274    }
2275
2276    #[test]
2277    fn test_icon_updated_in_source() {
2278        let mut destination_db = create_test_database();
2279
2280        let icon_id = Uuid::new_v4();
2281        destination_db
2282            .meta
2283            .custom_icons
2284            .insert(icon_id, CustomIcon::new(icon_id, None, Some(Times::epoch()), vec![1, 2, 3, 4]));
2285
2286        let mut source_db = destination_db.clone();
2287
2288        let source_icon = source_db.meta.custom_icons.get_mut(&icon_id).unwrap();
2289        source_icon.data = vec![5, 6, 7, 8];
2290        source_icon.last_modification_time = Some(Times::now());
2291
2292        let merge_result = destination_db.merge(&source_db).unwrap();
2293        assert_eq!(merge_result.warnings.len(), 0);
2294        assert_eq!(merge_result.events.len(), 1);
2295
2296        let icon = destination_db.meta.custom_icon(icon_id).unwrap();
2297        assert_eq!(icon.data, vec![5, 6, 7, 8]);
2298    }
2299
2300    #[test]
2301    fn test_icon_updated_in_destination() {
2302        let mut destination_db = create_test_database();
2303
2304        let icon_id = Uuid::new_v4();
2305        destination_db
2306            .meta
2307            .custom_icons
2308            .insert(icon_id, CustomIcon::new(icon_id, None, Some(Times::epoch()), vec![1, 2, 3, 4]));
2309
2310        let source_db = destination_db.clone();
2311
2312        let destination_icon = destination_db.meta.custom_icons.get_mut(&icon_id).unwrap();
2313        destination_icon.data = vec![5, 6, 7, 8];
2314        destination_icon.last_modification_time = Some(Times::now());
2315
2316        let merge_result = destination_db.merge(&source_db).unwrap();
2317        assert_eq!(merge_result.warnings.len(), 0);
2318        assert_eq!(merge_result.events.len(), 0);
2319
2320        let icon = destination_db.meta.custom_icon(icon_id).unwrap();
2321        assert_eq!(icon.data, vec![5, 6, 7, 8]);
2322    }
2323}