Skip to main content

keepass_ng/db/types/
group.rs

1use crate::db::{CustomDataItem, IconId, Times, entry::Entry, node::*, rc_refcell_node};
2use std::collections::HashMap;
3use uuid::Uuid;
4
5pub(crate) enum SearchField {
6    Uuid,
7    Title,
8}
9
10impl SearchField {
11    pub(crate) fn matches(&self, node: &NodePtr, field_value: &str) -> bool {
12        match self {
13            SearchField::Uuid => node.borrow().get_uuid().to_string() == field_value,
14            SearchField::Title => match node.borrow().get_title() {
15                Some(title) => title == field_value,
16                None => false,
17            },
18        }
19    }
20}
21
22/// A database group with child groups and entries
23#[derive(Debug, Clone)]
24#[cfg_attr(feature = "serialization", derive(serde::Serialize))]
25pub struct Group {
26    /// The unique identifier of the group
27    pub(crate) uuid: Uuid,
28
29    /// The name of the group
30    pub(crate) name: Option<String>,
31
32    /// Notes for the group
33    pub(crate) notes: Option<String>,
34
35    /// Tags assigned to the group
36    pub(crate) tags: Vec<String>,
37
38    /// ID of the group's icon
39    pub(crate) icon_id: Option<IconId>,
40
41    /// UUID for a custom group icon
42    pub(crate) custom_icon_uuid: Option<Uuid>,
43
44    /// The list of child nodes (Groups or Entries)
45    pub(crate) children: Vec<SerializableNodePtr>,
46
47    /// The list of time fields for this group
48    pub(crate) times: Times,
49
50    // Custom Data
51    pub(crate) custom_data: HashMap<String, CustomDataItem>,
52
53    /// Whether the group is expanded in the user interface
54    pub(crate) is_expanded: bool,
55
56    /// Default autotype sequence
57    pub(crate) default_autotype_sequence: Option<String>,
58
59    /// Whether autotype is enabled
60    pub(crate) enable_autotype: Option<bool>,
61
62    /// Whether searching is enabled
63    pub(crate) enable_searching: Option<bool>,
64
65    /// UUID for the last top visible entry
66    // TODO figure out what that is supposed to mean. According to the KeePass sourcecode, it has
67    // something to do with restoring selected items when re-opening a database.
68    pub(crate) last_top_visible_entry: Option<Uuid>,
69
70    pub(crate) parent: Option<Uuid>,
71
72    /// UUID of the group's previous parent
73    pub(crate) previous_parent_group: Option<Uuid>,
74}
75
76impl Default for Group {
77    fn default() -> Self {
78        Self {
79            uuid: Uuid::new_v4(),
80            name: Some("Default Group".to_string()),
81            notes: None,
82            tags: Vec::new(),
83            icon_id: Some(IconId::FOLDER),
84            custom_icon_uuid: None,
85            children: Vec::new(),
86            times: Times::new(),
87            custom_data: Default::default(),
88            is_expanded: false,
89            default_autotype_sequence: None,
90            enable_autotype: None,
91            enable_searching: None,
92            last_top_visible_entry: None,
93            parent: None,
94            previous_parent_group: None,
95        }
96    }
97}
98
99impl PartialEq for Group {
100    fn eq(&self, other: &Self) -> bool {
101        self.uuid == other.uuid
102            && self.compare_children(other)
103            && self.times == other.times
104            && self.name == other.name
105            && self.notes == other.notes
106            && self.icon_id == other.icon_id
107            && self.custom_icon_uuid == other.custom_icon_uuid
108            && self.is_expanded == other.is_expanded
109            && self.default_autotype_sequence == other.default_autotype_sequence
110            && self.enable_autotype == other.enable_autotype
111            && self.enable_searching == other.enable_searching
112            && self.last_top_visible_entry == other.last_top_visible_entry
113            && self.custom_data == other.custom_data
114        // && self.parent == other.parent
115    }
116}
117
118impl Eq for Group {}
119
120impl Node for Group {
121    fn duplicate(&self) -> NodePtr {
122        let mut new_group = self.clone();
123        new_group.parent = None;
124        new_group.children = self
125            .children
126            .iter()
127            .map(|child| {
128                let child = child.borrow().duplicate();
129                child.borrow_mut().set_parent(Some(new_group.uuid));
130                child.into()
131            })
132            .collect();
133        rc_refcell_node(new_group)
134    }
135
136    fn get_uuid(&self) -> Uuid {
137        self.uuid
138    }
139
140    fn set_uuid(&mut self, uuid: Uuid) {
141        self.uuid = uuid;
142    }
143
144    fn get_title(&self) -> Option<&str> {
145        self.name.as_deref()
146    }
147
148    fn set_title(&mut self, title: Option<&str>) {
149        self.name = title.map(std::string::ToString::to_string);
150    }
151
152    fn get_notes(&self) -> Option<&str> {
153        self.notes.as_deref()
154    }
155
156    fn set_notes(&mut self, notes: Option<&str>) {
157        self.notes = notes.map(std::string::ToString::to_string);
158    }
159
160    fn get_icon_id(&self) -> Option<IconId> {
161        self.icon_id
162    }
163
164    fn set_icon_id(&mut self, icon_id: Option<IconId>) {
165        self.icon_id = icon_id;
166    }
167
168    fn get_custom_icon_uuid(&self) -> Option<Uuid> {
169        self.custom_icon_uuid
170    }
171
172    fn get_times(&self) -> &Times {
173        &self.times
174    }
175
176    fn get_times_mut(&mut self) -> &mut Times {
177        &mut self.times
178    }
179
180    fn get_parent(&self) -> Option<Uuid> {
181        self.parent
182    }
183
184    fn set_parent(&mut self, parent: Option<Uuid>) {
185        self.parent = parent;
186    }
187}
188
189impl Group {
190    pub fn new(name: &str) -> Group {
191        Group {
192            name: Some(name.to_string()),
193            ..Group::default()
194        }
195    }
196
197    pub fn get_children(&self) -> Vec<NodePtr> {
198        self.children.iter().map(|c| c.into()).collect()
199    }
200
201    fn compare_children(&self, other: &Self) -> bool {
202        if self.children.len() != other.children.len() {
203            return false;
204        }
205        self.children.iter().zip(other.children.iter()).all(|(a, b)| {
206            if let (Some(a), Some(b)) = (a.borrow().downcast_ref::<Group>(), b.borrow().downcast_ref::<Group>()) {
207                a == b
208            } else if let (Some(a), Some(b)) = (a.borrow().downcast_ref::<Entry>(), b.borrow().downcast_ref::<Entry>()) {
209                a == b
210            } else {
211                false
212            }
213        })
214    }
215
216    pub fn set_name(&mut self, name: &str) {
217        self.name = Some(name.to_string());
218    }
219
220    pub fn tags(&self) -> &[String] {
221        &self.tags
222    }
223
224    pub fn get_tags_mut(&mut self) -> &mut Vec<String> {
225        &mut self.tags
226    }
227
228    pub fn custom_data(&self) -> &HashMap<String, CustomDataItem> {
229        &self.custom_data
230    }
231
232    pub fn custom_data_mut(&mut self) -> &mut HashMap<String, CustomDataItem> {
233        &mut self.custom_data
234    }
235
236    pub fn previous_parent_group(&self) -> Option<Uuid> {
237        self.previous_parent_group
238    }
239
240    pub fn add_child(&mut self, child: NodePtr, index: usize) {
241        child.borrow_mut().set_parent(Some(self.get_uuid()));
242        if index < self.children.len() {
243            self.children.insert(index, child.into());
244        } else {
245            self.children.push(child.into());
246        }
247    }
248
249    /// Recursively get a Group or Entry reference by specifying a path relative to the current Group
250    /// ```
251    /// use keepass_ng::{
252    ///     db::{with_node, Database, Entry, Group},
253    ///     DatabaseKey,
254    /// };
255    /// use std::fs::File;
256    ///
257    /// let mut file = File::open("tests/resources/test_db_with_password.kdbx").unwrap();
258    /// let db = Database::open(&mut file, DatabaseKey::new().with_password("demopass")).unwrap();
259    ///
260    /// let e = Group::get(&db.root, &["General", "Sample Entry #2"]).unwrap();
261    /// with_node::<Entry, _, _>(&e, |e| {
262    ///     println!("User: {}", e.get_username().unwrap());
263    /// });
264    /// ```
265    pub fn get(group: &NodePtr, path: &[&str]) -> Option<NodePtr> {
266        Self::get_internal(group, path, SearchField::Title)
267    }
268
269    pub fn get_by_uuid<T: AsRef<str>>(group: &NodePtr, path: &[T]) -> Option<NodePtr> {
270        Self::get_internal(group, path, SearchField::Uuid)
271    }
272
273    fn get_internal<T: AsRef<str>>(group: &NodePtr, path: &[T], search_field: SearchField) -> Option<NodePtr> {
274        if path.is_empty() {
275            Some(group.clone())
276        } else if path.len() == 1 {
277            group_get_children(group)
278                .unwrap_or_default()
279                .iter()
280                .find_map(|node| match search_field.matches(node, path[0].as_ref()) {
281                    true => Some(node.clone()),
282                    false => None,
283                })
284        } else {
285            let head = path[0].as_ref();
286            let tail = &path[1..path.len()];
287            let head_group = group_get_children(group).unwrap_or_default().iter().find_map(|node| {
288                if node_is_group(node) && search_field.matches(node, head) {
289                    Some(node.clone())
290                } else {
291                    None
292                }
293            })?;
294
295            Self::get_internal(&head_group, tail, search_field)
296        }
297    }
298
299    pub fn entries(&self) -> Vec<NodePtr> {
300        let mut response: Vec<NodePtr> = vec![];
301        for node in &self.children {
302            if node_is_entry(node) {
303                response.push(node.into());
304            }
305        }
306        response
307    }
308
309    pub fn groups(&self) -> Vec<NodePtr> {
310        let mut response: Vec<NodePtr> = vec![];
311        for node in &self.children {
312            if node_is_group(node) {
313                response.push(node.into());
314            }
315        }
316        response
317    }
318
319    pub fn reset_children(&mut self, children: Vec<NodePtr>) {
320        let uuid = self.get_uuid();
321        children.iter().for_each(|c| c.borrow_mut().set_parent(Some(uuid)));
322        self.children = children.into_iter().map(|c| c.into()).collect();
323    }
324}
325
326#[allow(unused_imports)]
327#[cfg(test)]
328mod group_tests {
329    use super::{Entry, Group, Node, Times};
330    #[cfg(feature = "merge")]
331    use crate::db::merge::entry_set_field_and_commit;
332    use crate::db::{rc_refcell_node, *};
333    use std::{thread, time};
334
335    #[cfg(feature = "merge")]
336    #[test]
337    fn test_merge_idempotence() {
338        let destination_group = rc_refcell_node(Group::new("group1"));
339        let entry = rc_refcell_node(Entry::default());
340        let _entry_uuid = entry.borrow().get_uuid();
341        entry_set_field_and_commit(&entry, "Title", "entry1").unwrap();
342        let count = group_get_children(&destination_group).unwrap().len();
343        group_add_child(&destination_group, entry, count).unwrap();
344
345        let source_group = destination_group.borrow().duplicate();
346
347        let sg2: NodePtr = source_group.clone();
348        let merge_result = Group::merge(&destination_group, &sg2).unwrap();
349        assert_eq!(merge_result.warnings.len(), 0);
350        assert_eq!(merge_result.events.len(), 0);
351
352        with_node::<Group, _, _>(&destination_group, |destination_group| {
353            assert_eq!(destination_group.children.len(), 1);
354            // The 2 groups should be exactly the same after merging, since
355            // nothing was performed during the merge.
356            with_node::<Group, _, _>(&source_group, |source_group| {
357                assert_eq!(destination_group, source_group);
358            });
359
360            let entry = destination_group.entries()[0].clone();
361            entry_set_field_and_commit(&entry, "Title", "entry1_updated").unwrap();
362        });
363        let merge_result = Group::merge(&destination_group, &sg2).unwrap();
364        assert_eq!(merge_result.warnings.len(), 0);
365        assert_eq!(merge_result.events.len(), 0);
366
367        let destination_group_just_after_merge = destination_group.borrow().duplicate();
368        let merge_result = Group::merge(&destination_group, &sg2).unwrap();
369        assert_eq!(merge_result.warnings.len(), 0);
370        assert_eq!(merge_result.events.len(), 0);
371
372        // Merging twice in a row, even if the first merge updated the destination group,
373        // should not create more changes.
374        assert!(node_is_equals_to(&destination_group_just_after_merge, &destination_group));
375    }
376
377    #[cfg(feature = "merge")]
378    #[test]
379    fn test_merge_add_new_entry() {
380        let destination_group = rc_refcell_node(Group::new("group1"));
381        let source_group = rc_refcell_node(Group::new("group1"));
382
383        let entry = rc_refcell_node(Entry::default());
384        let entry_uuid = entry.borrow().get_uuid();
385        entry_set_field_and_commit(&entry, "Title", "entry1").unwrap();
386        group_add_child(&source_group, entry, 0).unwrap();
387
388        let merge_result = Group::merge(&destination_group, &source_group).unwrap();
389        assert_eq!(merge_result.warnings.len(), 0);
390        assert_eq!(merge_result.events.len(), 1);
391        {
392            assert_eq!(group_get_children(&destination_group).unwrap().len(), 1);
393            let new_entry = search_node_by_uuid_with_specific_type::<Entry>(&destination_group, entry_uuid);
394            assert!(new_entry.is_some());
395            assert_eq!(new_entry.unwrap().borrow().get_title().unwrap(), "entry1");
396        }
397
398        // Merging the same group again should not create a duplicate entry.
399        let merge_result = Group::merge(&destination_group, &source_group).unwrap();
400        assert_eq!(merge_result.warnings.len(), 0);
401        assert_eq!(merge_result.events.len(), 0);
402        assert_eq!(group_get_children(&destination_group).unwrap().len(), 1);
403    }
404
405    #[cfg(feature = "merge")]
406    #[test]
407    fn test_merge_add_new_non_root_entry() {
408        let destination_group = rc_refcell_node(Group::new("group1"));
409        let destination_sub_group = rc_refcell_node(Group::new("subgroup1"));
410
411        group_add_child(&destination_group, destination_sub_group, 0).unwrap();
412
413        let source_group = destination_group.borrow().duplicate();
414        let source_sub_group = with_node::<Group, _, _>(&source_group, |g| g.groups()[0].clone()).unwrap();
415
416        let entry: NodePtr = rc_refcell_node(Entry::default());
417        let _entry_uuid = entry.borrow().get_uuid();
418        entry_set_field_and_commit(&entry, "Title", "entry1").unwrap();
419        let count = group_get_children(&source_sub_group).unwrap().len();
420        group_add_child(&source_sub_group, entry, count).unwrap();
421
422        let merge_result = Group::merge(&destination_group, &source_group).unwrap();
423        assert_eq!(merge_result.warnings.len(), 0);
424        assert_eq!(merge_result.events.len(), 1);
425        let destination_entries = with_node::<Group, _, _>(&destination_group, |g| g.get_all_entries(&[])).unwrap();
426        assert_eq!(destination_entries.len(), 1);
427        let (_created_entry, created_entry_location) = destination_entries.first().unwrap();
428        println!("{created_entry_location:?}");
429        assert_eq!(created_entry_location.len(), 2);
430    }
431
432    #[cfg(feature = "merge")]
433    #[test]
434    fn test_merge_add_new_entry_new_group() {
435        let destination_group = rc_refcell_node(Group::new("group1"));
436        let _destination_sub_group = rc_refcell_node(Group::new("subgroup1"));
437        let source_group = rc_refcell_node(Group::new("group1"));
438        let source_sub_group = rc_refcell_node(Group::new("subgroup1"));
439
440        let entry = rc_refcell_node(Entry::default());
441        let _entry_uuid = entry.borrow().get_uuid();
442        entry_set_field_and_commit(&entry, "Title", "entry1").unwrap();
443        group_add_child(&source_sub_group, entry, 0).unwrap();
444        group_add_child(&source_group, source_sub_group, 0).unwrap();
445
446        let merge_result = Group::merge(&destination_group, &source_group).unwrap();
447        assert_eq!(merge_result.warnings.len(), 0);
448        assert_eq!(merge_result.events.len(), 1);
449
450        with_node::<Group, _, _>(&destination_group, |destination_group| {
451            let destination_entries = destination_group.get_all_entries(&[]);
452            assert_eq!(destination_entries.len(), 1);
453            let (_, created_entry_location) = destination_entries.first().unwrap();
454            assert_eq!(created_entry_location.len(), 2);
455        });
456    }
457
458    #[cfg(feature = "merge")]
459    #[test]
460    fn test_merge_entry_relocation_existing_group() {
461        let entry = rc_refcell_node(Entry::default());
462        let entry_uuid = entry.borrow().get_uuid();
463        entry_set_field_and_commit(&entry, "Title", "entry1").unwrap();
464
465        let destination_group = rc_refcell_node(Group::new("group1"));
466        let destination_sub_group1 = rc_refcell_node(Group::new("subgroup1"));
467        let destination_sub_group2 = rc_refcell_node(Group::new("subgroup2"));
468        let destination_sub_group2_uuid = destination_sub_group2.borrow().get_uuid();
469        group_add_child(&destination_sub_group1, entry, 0).unwrap();
470        group_add_child(&destination_group, destination_sub_group1.borrow().duplicate(), 0).unwrap();
471        group_add_child(&destination_group, destination_sub_group2.borrow().duplicate(), 1).unwrap();
472
473        let source_group = destination_group.borrow().duplicate();
474        assert_eq!(
475            with_node::<Group, _, _>(&source_group, |g| g.get_all_entries(&[])).unwrap().len(),
476            1
477        );
478
479        let destination_group_uuid = destination_group.borrow().get_uuid();
480        let destination_sub_group1_uuid = destination_sub_group1.borrow().get_uuid();
481
482        let location = vec![destination_group_uuid, destination_sub_group1_uuid];
483        let removed_entry = Group::remove_entry(&source_group, entry_uuid, &location).unwrap();
484
485        removed_entry.borrow_mut().get_times_mut().set_location_changed(Some(Times::now()));
486        assert!(
487            with_node::<Group, _, _>(&source_group, |g| g.get_all_entries(&[]))
488                .unwrap()
489                .is_empty()
490        );
491        // FIXME we should not have to update the history here. We should
492        // have a better compare function in the merge function instead.
493        with_node_mut::<Entry, _, _>(&removed_entry, |entry| {
494            entry.update_history();
495        });
496
497        let location = vec![destination_group_uuid, destination_sub_group2_uuid];
498
499        Group::insert_entry(&source_group, removed_entry, &location).unwrap();
500
501        let merge_result = Group::merge(&destination_group, &source_group).unwrap();
502        assert_eq!(merge_result.warnings.len(), 0);
503        assert_eq!(merge_result.events.len(), 1);
504
505        let destination_entries = with_node::<Group, _, _>(&destination_group, |g| g.get_all_entries(&[])).unwrap();
506        assert_eq!(destination_entries.len(), 1);
507        let (_moved_entry, moved_entry_location) = destination_entries.first().unwrap();
508        assert_eq!(moved_entry_location.len(), 2);
509        assert_eq!(moved_entry_location[0], destination_group_uuid);
510        assert_eq!(moved_entry_location[1], destination_sub_group2_uuid);
511    }
512
513    #[cfg(feature = "merge")]
514    #[test]
515    fn test_merge_entry_relocation_new_group() {
516        let entry = rc_refcell_node(Entry::default());
517        let _entry_uuid = entry.borrow().get_uuid();
518        entry_set_field_and_commit(&entry, "Title", "entry1").unwrap();
519
520        let destination_group = rc_refcell_node(Group::new("group1"));
521        let uuid1 = destination_group.borrow().get_uuid();
522        let destination_sub_group = rc_refcell_node(Group::new("subgroup1"));
523        group_add_child(&destination_sub_group, entry.borrow().duplicate(), 0).unwrap();
524        group_add_child(&destination_group, destination_sub_group, 0).unwrap();
525
526        let source_group = destination_group.borrow().duplicate();
527        let source_sub_group = rc_refcell_node(Group::new("subgroup2"));
528        let uuid2 = source_sub_group.borrow().get_uuid();
529        thread::sleep(time::Duration::from_secs(1));
530        with_node_mut::<Entry, _, _>(&entry, |entry| {
531            entry.times.set_location_changed(Some(Times::now()));
532            // FIXME we should not have to update the history here. We should
533            // have a better compare function in the merge function instead.
534            entry.update_history();
535        });
536        group_add_child(&source_sub_group, entry, 0).unwrap();
537        with_node_mut::<Group, _, _>(&source_group, |g| {
538            g.reset_children(vec![]);
539            g.add_child(source_sub_group, 0);
540        })
541        .unwrap();
542
543        let merge_result = Group::merge(&destination_group, &source_group).unwrap();
544        assert_eq!(merge_result.warnings.len(), 0);
545        assert_eq!(merge_result.events.len(), 1);
546
547        let destination_entries = with_node::<Group, _, _>(&destination_group, |g| g.get_all_entries(&[])).unwrap();
548        assert_eq!(destination_entries.len(), 1);
549        let (_, created_entry_location) = destination_entries.first().unwrap();
550        assert_eq!(created_entry_location.len(), 2);
551        assert_eq!(created_entry_location[0], uuid1);
552        assert_eq!(created_entry_location[1], uuid2);
553    }
554
555    #[cfg(feature = "merge")]
556    #[test]
557    fn test_update_in_destination_no_conflict() {
558        let destination_group = rc_refcell_node(Group::new("group1"));
559
560        let entry = rc_refcell_node(Entry::default());
561        let _entry_uuid = entry.borrow().get_uuid();
562        entry_set_field_and_commit(&entry, "Title", "entry1").unwrap();
563
564        group_add_child(&destination_group, entry, 0).unwrap();
565
566        let source_group = destination_group.borrow().duplicate();
567
568        let entry = with_node::<Group, _, _>(&destination_group, |g| g.entries()[0].clone()).unwrap();
569        entry_set_field_and_commit(&entry, "Title", "entry1_updated").unwrap();
570
571        let merge_result = Group::merge(&destination_group, &source_group).unwrap();
572        assert_eq!(merge_result.warnings.len(), 0);
573        assert_eq!(merge_result.events.len(), 0);
574
575        let entry = with_node::<Group, _, _>(&destination_group, |g| g.entries()[0].clone()).unwrap();
576        assert_eq!(entry.borrow().get_title(), Some("entry1_updated"));
577    }
578
579    #[cfg(feature = "merge")]
580    #[test]
581    fn test_update_in_source_no_conflict() {
582        let destination_group = rc_refcell_node(Group::new("group1"));
583
584        let entry = rc_refcell_node(Entry::default());
585        let _entry_uuid = entry.borrow().get_uuid();
586        entry_set_field_and_commit(&entry, "Title", "entry1").unwrap();
587        group_add_child(&destination_group, entry, 0).unwrap();
588
589        let source_group = destination_group.borrow().duplicate();
590
591        let entry = with_node::<Group, _, _>(&source_group, |g| g.entries()[0].clone()).unwrap();
592        entry_set_field_and_commit(&entry, "Title", "entry1_updated").unwrap();
593
594        let merge_result = Group::merge(&destination_group, &source_group).unwrap();
595        assert_eq!(merge_result.warnings.len(), 0);
596        assert_eq!(merge_result.events.len(), 1);
597
598        let entry = with_node::<Group, _, _>(&destination_group, |g| g.entries()[0].clone()).unwrap();
599        assert_eq!(entry.borrow().get_title(), Some("entry1_updated"));
600    }
601
602    #[cfg(feature = "merge")]
603    #[test]
604    fn test_update_with_conflicts() {
605        let destination_group = rc_refcell_node(Group::new("group1"));
606
607        let entry = rc_refcell_node(Entry::default());
608        let _entry_uuid = entry.borrow().get_uuid();
609        entry_set_field_and_commit(&entry, "Title", "entry1").unwrap();
610        group_add_child(&destination_group, entry, 0).unwrap();
611
612        let source_group = destination_group.borrow().duplicate();
613
614        let entry = with_node::<Group, _, _>(&destination_group, |g| g.entries()[0].clone()).unwrap();
615        entry_set_field_and_commit(&entry, "Title", "entry1_updated_from_destination").unwrap();
616
617        let entry = with_node::<Group, _, _>(&source_group, |g| g.entries()[0].clone()).unwrap();
618        entry_set_field_and_commit(&entry, "Title", "entry1_updated_from_source").unwrap();
619
620        let merge_result = Group::merge(&destination_group, &source_group).unwrap();
621        assert_eq!(merge_result.warnings.len(), 0);
622        assert_eq!(merge_result.events.len(), 1);
623
624        let entry = with_node::<Group, _, _>(&destination_group, |g| g.entries()[0].clone()).unwrap();
625        assert_eq!(entry.borrow().get_title(), Some("entry1_updated_from_source"));
626
627        let merged_history = with_node::<Entry, _, _>(&entry, |e| e.history.clone().unwrap()).unwrap();
628        assert!(merged_history.is_ordered());
629        assert_eq!(merged_history.entries.len(), 3);
630        let merged_entry = &merged_history.entries[1];
631        assert_eq!(merged_entry.get_title(), Some("entry1_updated_from_destination"));
632
633        // Merging again should not result in any additional change.
634        let destination_group_dup = destination_group.borrow().duplicate();
635        let merge_result = Group::merge(&destination_group, &destination_group_dup).unwrap();
636        assert_eq!(merge_result.warnings.len(), 0);
637        assert_eq!(merge_result.events.len(), 0);
638    }
639
640    #[test]
641    fn get() {
642        let db = Database::new(Default::default());
643
644        let general_group = rc_refcell_node(Group::new("General"));
645        let sample_entry = rc_refcell_node(Entry::default());
646        sample_entry.borrow_mut().set_title(Some("Sample Entry #2"));
647        group_add_child(&general_group, sample_entry, 0).unwrap();
648        group_add_child(&db.root, general_group, 0).unwrap();
649
650        assert!(Group::get(&db.root, &["General", "Sample Entry #2"]).is_some());
651        assert!(Group::get(&db.root, &["General"]).is_some());
652        assert!(Group::get(&db.root, &["Invalid Group"]).is_none());
653        assert!(Group::get(&db.root, &[]).is_some());
654    }
655
656    #[test]
657    fn get_by_uuid() {
658        let db = Database::new(Default::default());
659
660        let general_group = rc_refcell_node(Group::new("General"));
661        let general_group_uuid = general_group.borrow().get_uuid().to_string();
662        let sample_entry = rc_refcell_node(Entry::default());
663        let sample_entry_uuid = sample_entry.borrow().get_uuid().to_string();
664        sample_entry.borrow_mut().set_title(Some("Sample Entry #2"));
665        group_add_child(&general_group, sample_entry, 0).unwrap();
666        group_add_child(&db.root, general_group, 0).unwrap();
667
668        let invalid_uuid = uuid::Uuid::new_v4().to_string();
669
670        // Testing with references to the UUIDs
671        let group_path: [&str; 1] = [general_group_uuid.as_ref()];
672        let entry_path: [&str; 2] = [general_group_uuid.as_ref(), sample_entry_uuid.as_ref()];
673        let invalid_path: [&str; 1] = [invalid_uuid.as_ref()];
674        let empty_path: [&str; 0] = [];
675
676        assert!(Group::get_by_uuid(&db.root, &group_path).is_some());
677        assert!(Group::get_by_uuid(&db.root, &entry_path).is_some());
678        assert!(Group::get_by_uuid(&db.root, &invalid_path).is_none());
679        assert!(Group::get_by_uuid(&db.root, &empty_path).is_some());
680
681        // Testing with owned versions of the UUIDs.
682        let group_path = vec![general_group_uuid.clone()];
683        let entry_path = vec![general_group_uuid.clone(), sample_entry_uuid.clone()];
684        let invalid_path = vec![invalid_uuid.clone()];
685        let empty_path: Vec<String> = vec![];
686
687        assert!(Group::get_by_uuid(&db.root, &group_path).is_some());
688        assert!(Group::get_by_uuid(&db.root, &entry_path).is_some());
689        assert!(Group::get_by_uuid(&db.root, &invalid_path).is_none());
690        assert!(Group::get_by_uuid(&db.root, &empty_path).is_some());
691    }
692}