Skip to main content

keepass_ng/db/types/
group.rs

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