3dbb2cbe5945f48e5b5726918fb647bdc3e61cb0
[platform/upstream/folks.git] / tests / eds / link-personas.vala
1 /*
2  * Copyright (C) 2011 Collabora Ltd.
3  *
4  * This library is free software: you can redistribute it and/or modify
5  * it under the terms of the GNU Lesser General Public License as published by
6  * the Free Software Foundation, either version 2.1 of the License, or
7  * (at your option) any later version.
8  *
9  * This library is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12  * GNU Lesser General Public License for more details.
13  *
14  * You should have received a copy of the GNU Lesser General Public License
15  * along with this library.  If not, see <http://www.gnu.org/licenses/>.
16  *
17  * Authors: Raul Gutierrez Segales <raul.gutierrez.segales@collabora.co.uk>
18  *
19  */
20
21 using Folks;
22 using Gee;
23
24 enum LinkingMethod
25 {
26   IM_ADDRESSES,
27   LOCAL_IDS,
28   WEB_SERVICE_ADDRESSES,
29   EMAIL_AS_IM_ADDRESS
30 }
31
32
33 public class LinkPersonasTests : Folks.TestCase
34 {
35   private GLib.MainLoop _main_loop;
36   private EdsTest.Backend? _eds_backend;
37   private IndividualAggregator _aggregator;
38   private string _persona_fullname_1;
39   private string _persona_fullname_2;
40   private string _im_address_1 = "someone-1@jabber.example.org";
41   private string _im_address_2 = "someone-2@jabber.example.org";
42   private string _auto_linkable_email = "the.cool.dude@gmail.tld";
43   private bool _linking_fired;
44   private bool _persona_found_1;
45   private bool _persona_found_2;
46   private string _persona_iid_1;
47   private string _persona_iid_2;
48   private HashSet<Persona> _personas;
49   private Gee.HashMap<string, string> _linking_props;
50   private LinkingMethod _linking_method;
51   private int _test_num = -1;
52
53   public LinkPersonasTests ()
54     {
55       base ("LinkPersonasTests");
56
57       this.add_test ("test linking personas via IM addresses",
58           this.test_linking_personas_via_im_addresses);
59       this.add_test ("test linking personas via local IDs",
60           this.test_linking_personas_via_local_ids);
61       this.add_test ("test linking personas via web service addresses",
62           this.test_linking_personas_via_web_service_addresses);
63       this.add_test ("test auto linking via e-mail address as IM address",
64           this.test_linking_via_email_as_im_address);
65     }
66
67   public override void set_up ()
68     {
69       this._eds_backend = new EdsTest.Backend ();
70
71       /* Create a new backend (by name) each set up to guarantee we don't
72        * inherit state from the last test.
73        * FIXME: bgo#690830 */
74       this._test_num++;
75       this._eds_backend.set_up (false, @"test$_test_num");
76
77       /* We configure eds as the primary store */
78       var config_val = "eds:%s".printf (this._eds_backend.address_book_uid);
79       Environment.set_variable ("FOLKS_PRIMARY_STORE", config_val, true);
80     }
81
82   public override void tear_down ()
83     {
84       this._eds_backend.tear_down ();
85
86       Environment.unset_variable ("FOLKS_PRIMARY_STORE");
87
88       this._eds_backend = null;
89     }
90
91   public void test_linking_personas_via_im_addresses ()
92     {
93       this._linking_method = LinkingMethod.IM_ADDRESSES;
94       this._test_linking_personas ();
95     }
96
97   public void test_linking_personas_via_local_ids ()
98     {
99       this._linking_method = LinkingMethod.LOCAL_IDS;
100       this._test_linking_personas ();
101     }
102
103   public void test_linking_personas_via_web_service_addresses ()
104     {
105       this._linking_method = LinkingMethod.WEB_SERVICE_ADDRESSES;
106       this._test_linking_personas ();
107     }
108
109   public void test_linking_via_email_as_im_address ()
110     {
111       this._linking_method = LinkingMethod.EMAIL_AS_IM_ADDRESS;
112       this._test_linking_personas ();
113     }
114
115   private void _test_linking_personas ()
116     {
117       this._main_loop = new GLib.MainLoop (null, false);
118       this._persona_fullname_1 = "persona #1";
119       this._persona_fullname_2 = "persona #2";
120       this._personas = new HashSet<Persona> ();
121       this._persona_found_1 = false;
122       this._persona_found_2 = false;
123       this._linking_fired = false;
124       this._persona_iid_1 = "";
125       this._persona_iid_2 = "";
126
127       this._linking_props = new Gee.HashMap<string, string> ();
128       if (this._linking_method == LinkingMethod.IM_ADDRESSES ||
129           this._linking_method == LinkingMethod.WEB_SERVICE_ADDRESSES)
130         {
131           this._linking_props.set ("prop1", this._im_address_1);
132           this._linking_props.set ("prop2", this._im_address_2);
133         }
134       else if (this._linking_method == LinkingMethod.EMAIL_AS_IM_ADDRESS)
135         {
136           this._linking_props.set ("prop1", this._auto_linkable_email);
137           this._linking_props.set ("prop2", this._auto_linkable_email);
138         }
139
140       this._test_linking_personas_async.begin ();
141
142       var timer_id = Timeout.add_seconds (8, () =>
143         {
144           // Let the main loop run out of events before we quit (yes, this is a HACK)
145           Timeout.add_seconds (5, () =>
146             {
147               this._main_loop.quit ();
148               return false;
149             });
150           assert_not_reached ();
151         });
152
153       this._main_loop.run ();
154
155       GLib.Source.remove (timer_id);
156       this._aggregator = null;
157       this._main_loop = null;
158     }
159
160   private async void _test_linking_personas_async ()
161     {
162       var store = BackendStore.dup ();
163       yield store.prepare ();
164       this._aggregator = new IndividualAggregator ();
165       this._aggregator.individuals_changed_detailed.connect
166           (this._individuals_changed_cb);
167       try
168         {
169           yield this._aggregator.prepare ();
170
171           var pstore = this._get_store (store,
172               this._eds_backend.address_book_uid);
173           assert (pstore != null);
174
175           pstore.notify["is-prepared"].connect (this._notify_pstore_cb);
176           if (pstore.is_prepared)
177             yield this._add_personas (pstore, pstore);
178         }
179       catch (GLib.Error e)
180         {
181           GLib.warning ("Error when calling prepare: %s\n", e.message);
182         }
183     }
184
185   private void _notify_pstore_cb (Object _pstore, ParamSpec ps)
186     {
187       var pstore = (PersonaStore) _pstore;
188       this._add_personas.begin (pstore, pstore);
189     }
190
191   private PersonaStore? _get_store (BackendStore store, string store_id)
192     {
193       PersonaStore? pstore = null;
194       foreach (var backend in store.enabled_backends.values)
195         {
196           pstore = backend.persona_stores.get (store_id);
197           if (pstore != null)
198             break;
199         }
200       return pstore;
201     }
202
203   /* Here is how this test is expected to work:
204    * - we start by adding 2 personas
205    * - this should trigger individuals-changed with 2 new individuals
206    * - we ask the IndividualAggregator to link the 2 personas coming
207    *   from those individuals
208    * - we wait for a new Individual which contains the linkable
209    *   attributes of these 2 personas
210    *
211    * @param pstore1 the {@link PersonaStore} in which to add the 1st Persona
212    * @param pstore2 the {@link PersonaStore} in which to add the 1st Persona
213    */
214   private async void _add_personas (PersonaStore pstore1, PersonaStore pstore2)
215     {
216       HashTable<string, Value?> details1 = new HashTable<string, Value?>
217           (str_hash, str_equal);
218       Value? v1;
219       var wsk =
220         Folks.PersonaStore.detail_key (PersonaDetail.WEB_SERVICE_ADDRESSES);
221
222       if (this._linking_method == LinkingMethod.IM_ADDRESSES ||
223           this._linking_method == LinkingMethod.EMAIL_AS_IM_ADDRESS)
224         {
225           v1 = Value (typeof (MultiMap));
226           var im_addrs1 = new HashMultiMap<string, ImFieldDetails> (
227               null, null,
228               (Gee.HashDataFunc) AbstractFieldDetails<string>.hash_static,
229               (Gee.EqualDataFunc) AbstractFieldDetails<string>.equal_static);
230           if (this._linking_method == LinkingMethod.EMAIL_AS_IM_ADDRESS)
231             im_addrs1.set ("jabber",
232                 new ImFieldDetails (this._auto_linkable_email));
233           else
234             im_addrs1.set ("jabber", new ImFieldDetails (this._im_address_1));
235           v1.set_object (im_addrs1);
236           details1.insert ("im-addresses", (owned) v1);
237         }
238       else if (this._linking_method == LinkingMethod.WEB_SERVICE_ADDRESSES)
239         {
240           v1 = Value (typeof (MultiMap));
241           var wsa1 = new HashMultiMap<string, WebServiceFieldDetails> (
242               null, null,
243               (Gee.HashDataFunc) AbstractFieldDetails<string>.hash_static,
244               (Gee.EqualDataFunc) AbstractFieldDetails<string>.equal_static);
245           wsa1.set ("twitter", new WebServiceFieldDetails (this._im_address_1));
246           v1.set_object (wsa1);
247           details1.insert (wsk, (owned) v1);
248         }
249
250       Value? v2 = Value (typeof (string));
251       v2.set_string (this._persona_fullname_1);
252       details1.insert ("full-name", (owned) v2);
253
254       HashTable<string, Value?> details2 = new HashTable<string, Value?>
255           (str_hash, str_equal);
256
257       Value? v3;
258       if (this._linking_method == LinkingMethod.IM_ADDRESSES)
259         {
260           v3 = Value (typeof (MultiMap));
261           var im_addrs2 = new HashMultiMap<string, ImFieldDetails> (
262               null, null,
263               (Gee.HashDataFunc) AbstractFieldDetails<string>.hash_static,
264               (Gee.EqualDataFunc) AbstractFieldDetails<string>.equal_static);
265           im_addrs2.set ("yahoo", new ImFieldDetails (this._im_address_2));
266           v3.set_object (im_addrs2);
267           details2.insert ("im-addresses", (owned) v3);
268         }
269       else if (this._linking_method == LinkingMethod.WEB_SERVICE_ADDRESSES)
270         {
271           v3 = Value (typeof (MultiMap));
272           var wsa2 = new HashMultiMap<string, WebServiceFieldDetails> (
273               null, null,
274               (Gee.HashDataFunc) AbstractFieldDetails<string>.hash_static,
275               (Gee.EqualDataFunc) AbstractFieldDetails<string>.equal_static);
276           wsa2.set ("lastfm", new WebServiceFieldDetails (this._im_address_2));
277           v3.set_object (wsa2);
278           details2.insert (wsk, (owned) v3);
279         }
280       else if (this._linking_method == LinkingMethod.EMAIL_AS_IM_ADDRESS)
281         {
282           v3 = Value (typeof (Set));
283           var emails = new HashSet<EmailFieldDetails> (
284               AbstractFieldDetails<string>.hash_static,
285               AbstractFieldDetails<string>.equal_static);
286           var email_1 = new EmailFieldDetails (this._auto_linkable_email);
287           emails.add (email_1);
288           v3.set_object (emails);
289           details2.insert (
290               Folks.PersonaStore.detail_key (PersonaDetail.EMAIL_ADDRESSES),
291               (owned) v3);
292         }
293
294       Value? v4 = Value (typeof (string));
295       v4.set_string (this._persona_fullname_2);
296       details2.insert ("full-name", (owned)v4);
297
298       try
299         {
300           yield this._aggregator.add_persona_from_details (null,
301               pstore1, details1);
302
303           yield this._aggregator.add_persona_from_details (null,
304               pstore2, details2);
305         }
306       catch (Folks.IndividualAggregatorError e)
307         {
308           GLib.warning ("[AddPersonaError] add_persona_from_details: %s\n",
309               e.message);
310         }
311     }
312
313   private void _individuals_changed_cb (
314        MultiMap<Individual?, Individual?> changes)
315     {
316       this._individuals_changed_async (changes);
317     }
318
319   private void _individuals_changed_async (
320        MultiMap<Individual?, Individual?> changes)
321     {
322       var added = changes.get_values ();
323
324       foreach (var i in added)
325         {
326           if (i == null)
327             {
328               continue;
329             }
330
331           if (this._linking_method == LinkingMethod.EMAIL_AS_IM_ADDRESS)
332             this._check_auto_linked_personas.begin (i);
333           else
334             this._check_personas.begin (i);
335         }
336     }
337
338   /* As mentioned in _add_personas here we actually check
339    * for the following events
340    *
341    * - spot the 2 individuals corresponding to the 2 personas we've added
342    * - when we've spotted these 2, we pack them in a list and feed that to
343    *   IndividualAggregator#link_personas
344    * - this should fire a new individuals-changed event with a new individual
345    *   which should be the linked individual if it contains the linking
346    *   properties of the 2 linked personas.
347    */
348   private async void _check_personas (Individual i)
349     {
350       if (this._linking_props.size == 0 &&
351           this._linking_fired)
352         {
353           /* All is done. */
354           return;
355         }
356
357       Persona first_persona = null;
358       foreach (var p in i.personas)
359         {
360           first_persona = p;
361           break;
362         }
363
364       if (i.full_name == this._persona_fullname_1 &&
365           this._persona_iid_1 == "")
366         {
367           this._persona_iid_1 = first_persona.iid;
368           this._personas.add (first_persona);
369           if (this._linking_method == LinkingMethod.LOCAL_IDS)
370             {
371               var contact_id1 = ((Edsf.Persona) first_persona).iid;
372               this._linking_props.set ("prop1", contact_id1);
373             }
374         }
375       else if (i.full_name == this._persona_fullname_2 &&
376           this._persona_iid_2 == "")
377         {
378           this._persona_iid_2 = first_persona.iid;
379           this._personas.add (first_persona);
380           if (this._linking_method == LinkingMethod.LOCAL_IDS)
381             {
382               var contact_id2 = ((Edsf.Persona) first_persona).iid;
383               this._linking_props.set ("prop2", contact_id2);
384             }
385         }
386       else if (i.personas.size > 1)
387         {
388           /* Lets check if it contains all the linking properties */
389           if (this._linking_method == LinkingMethod.IM_ADDRESSES)
390             {
391               foreach (var proto in i.im_addresses.get_keys ())
392                 {
393                   var im_fds = i.im_addresses.get (proto);
394                   foreach (var im_fd in im_fds)
395                     {
396                       if (im_fd.value == this._linking_props.get ("prop1"))
397                         {
398                           this._linking_props.unset ("prop1");
399                         }
400                       else if (im_fd.value == this._linking_props.get ("prop2"))
401                         {
402                           this._linking_props.unset ("prop2");
403                         }
404                     }
405                 }
406             }
407           else if (this._linking_method == LinkingMethod.LOCAL_IDS)
408             {
409               foreach (var local_id in i.local_ids)
410                 {
411                   if (local_id == this._linking_props.get ("prop1"))
412                     {
413                       this._linking_props.unset ("prop1");
414                     }
415                   else if (local_id == this._linking_props.get ("prop2"))
416                     {
417                       this._linking_props.unset ("prop2");
418                     }
419                 }
420             }
421           else if (this._linking_method == LinkingMethod.WEB_SERVICE_ADDRESSES)
422             {
423               foreach (var service in i.web_service_addresses.get_keys ())
424                 {
425                   var ws_fds = i.web_service_addresses.get (service);
426                   foreach (var ws_fd in ws_fds)
427                     {
428                       var prop1 = this._linking_props.get ("prop1");
429                       var prop2 = this._linking_props.get ("prop2");
430                       if (prop1 != null &&
431                           ws_fd.equal (new WebServiceFieldDetails (prop1)))
432                         {
433                           this._linking_props.unset ("prop1");
434                         }
435                       else if (prop2 != null &&
436                           ws_fd.equal (new WebServiceFieldDetails (prop2)))
437                         {
438                           this._linking_props.unset ("prop2");
439                         }
440                     }
441                 }
442             }
443
444           if (this._linking_props.size == 0)
445             {
446               Timeout.add_seconds (5, () =>
447                 {
448                   this._main_loop.quit ();
449                   return false;
450                 });
451             }
452         }
453
454       /* We can try linking the personas only once we've got the
455        * 2 initially created personas. */
456       if (this._personas.size == 2 &&
457           this._linking_fired == false)
458         {
459           this._linking_fired = true;
460           try
461             {
462               yield this._aggregator.link_personas (this._personas);
463             }
464           catch (GLib.Error e)
465             {
466               GLib.warning ("link_personas: %s\n", e.message);
467             }
468         }
469     }
470
471   /* Certain e-mail addresses (i.e.: gmail, msn) will be added
472    * as IM addresses to their Persona so auto linking should
473    * happen.
474    *
475    * Hence, no need to call link_personas () here.
476    */
477   private async void _check_auto_linked_personas (Individual i)
478     {
479       if (this._linking_props.size == 0)
480         {
481           /* Don't even bother. */
482           return;
483         }
484
485       if (i.personas.size > 1)
486         {
487           foreach (var email in i.email_addresses)
488             {
489               if (email.value == this._auto_linkable_email)
490                 {
491                   this._linking_props.unset ("prop1");
492                 }
493             }
494
495           foreach (var proto1 in i.im_addresses.get_keys ())
496             {
497               var im_fds1 = i.im_addresses.get (proto1);
498               foreach (var im_fd1 in im_fds1)
499                 {
500                   if (im_fd1.value == this._auto_linkable_email)
501                     {
502                       this._linking_props.unset ("prop2");
503                     }
504                 }
505             }
506         }
507
508       if (this._linking_props.size == 0)
509         {
510           this._main_loop.quit ();
511         }
512     }
513 }
514
515 public int main (string[] args)
516 {
517   Test.init (ref args);
518
519   TestSuite root = TestSuite.get_root ();
520   root.add_suite (new LinkPersonasTests ().get_suite ());
521
522   Test.run ();
523
524   return 0;
525 }