/* * Copyright (C) 2011 Collabora Ltd. * * This library is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 2.1 of the License, or * (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this library. If not, see . * * Authors: Raul Gutierrez Segales * */ using Tracker.Sparql; using TrackerTest; using Folks; using Gee; public class LinkPersonasTests : TrackerTest.TestCase { private GLib.MainLoop _main_loop; private IndividualAggregator _aggregator; private string _persona_fullname_1; private string _persona_fullname_2; private string _proto = "jabber"; private string _im_address_1 = "someone-1@jabber.example.org"; private string _im_address_2 = "someone-2@jabber.example.org"; private bool _linking_fired; private bool _persona_found_1; private bool _persona_found_2; private string _persona_iid_1 = ""; private string _persona_iid_2 = ""; private HashSet _personas; private Gee.HashMap _linking_props; public LinkPersonasTests () { base ("LinkPersonasTests"); this.add_test ("test linking personas", this.test_linking_personas); } public override void set_up () { base.set_up (); Environment.set_variable ("FOLKS_PRIMARY_STORE", "tracker", true); /* FIXME: this set_up method takes care both of setting * the connection with Tracker and adding the contacts * needed for the tests. We might need to trigger those * actions at separate points so we should decouple them. */ ((!) this.tracker_backend).set_up (); } public override void tear_down () { Environment.unset_variable ("FOLKS_PRIMARY_STORE"); base.tear_down (); } public void test_linking_personas () { this._main_loop = new GLib.MainLoop (null, false); this._persona_fullname_1 = "persona #1"; this._persona_fullname_2 = "persona #2"; this._personas = new HashSet (); this._persona_found_1 = false; this._persona_found_2 = false; this._linking_fired = false; this._linking_props = new Gee.HashMap (); this._linking_props.set ("prop1", this._im_address_1); this._linking_props.set ("prop2", this._im_address_2); this._test_linking_personas_async.begin (); /* Kill the main loop after 5 seconds: if the linked individual hasn't * show up at this point then we've either seen an error or we've been * too slow (which we can consider to be failure). */ TestUtils.loop_run_with_timeout (this._main_loop); /* Check we get the new individual (containing the linked * personas) and that the previous ones were removed. */ assert (this._linking_props.size == 0); } private async void _test_linking_personas_async () { var store = BackendStore.dup (); yield store.prepare (); this._aggregator = IndividualAggregator.dup (); this._aggregator.individuals_changed_detailed.connect (this._individuals_changed_cb); try { yield this._aggregator.prepare (); PersonaStore pstore = null; foreach (var backend in store.enabled_backends.values) { pstore = backend.persona_stores.get ("tracker"); if (pstore != null) break; } assert (pstore != null); pstore.notify["is-prepared"].connect (this._persona_store_prepared_cb); } catch (GLib.Error e) { GLib.warning ("Error when calling prepare: %s\n", e.message); } } private void _persona_store_prepared_cb (Object obj, ParamSpec params) { PersonaStore pstore = (!)(obj as PersonaStore); _add_personas.begin (pstore); } /* Here is how this test is expected to work: * - we start by adding 2 personas * - this should trigger individuals-changed with 2 new individuals * - we ask the IndividualAggregator to link the 2 personas coming * from those individuals * - we wait for a new Individual which contains the linkable * attributes of these 2 personas */ private async void _add_personas (PersonaStore pstore) { HashTable details1 = new HashTable (str_hash, str_equal); Value? v1 = Value (typeof (MultiMap)); var im_addrs1 = new HashMultiMap (null, null, AbstractFieldDetails.hash_static, AbstractFieldDetails.equal_static); im_addrs1.set (this._proto, new ImFieldDetails (this._im_address_1)); v1.set_object (im_addrs1); details1.insert ("im-addresses", (owned) v1); Value? v2 = Value (typeof (string)); v2.set_string (this._persona_fullname_1); details1.insert ("full-name", (owned) v2); HashTable details2 = new HashTable (str_hash, str_equal); Value? v3 = Value (typeof (MultiMap)); var im_addrs2 = new HashMultiMap (null, null, AbstractFieldDetails.hash_static, AbstractFieldDetails.equal_static); im_addrs2.set (this._proto, new ImFieldDetails (this._im_address_2)); v3.set_object (im_addrs2); details2.insert ("im-addresses", (owned) v3); Value? v4 = Value (typeof (string)); v4.set_string (this._persona_fullname_2); details2.insert ("full-name", (owned)v4); try { yield this._aggregator.add_persona_from_details (null, pstore, details1); yield this._aggregator.add_persona_from_details (null, pstore, details2); } catch (Folks.IndividualAggregatorError e) { GLib.warning ("[AddPersonaError] add_persona_from_details: %s\n", e.message); } } private void _individuals_changed_cb ( MultiMap changes) { var added = changes.get_values (); foreach (var i in added) { if (i == null) { continue; } /* Lets listen to notifications from those individuals * which aren't the default (Tracker) user */ if (!i.is_user) { this._check_personas (i); i.notify["full-name"].connect (this._notify_cb); i.notify["im-addresses"].connect (this._notify_cb); } } } private void _notify_cb (Object individual_obj, ParamSpec ps) { Folks.Individual i = (Folks.Individual) individual_obj; this._check_personas (i); } /* As mentioned in _add_personas here we actually check * for the following events * * - spot the 2 individuals corresponding to the 2 personas we've added * - when we've spotted these 2, we pack them in a list and feed that to * IndividualAggregator#link_personas * - this should fire a new individuals-changed event with a new individual * which should be the linked individual if it contains the linking * properties of the 2 linked personas. */ private void _check_personas (Individual i) { Persona first_persona = null; foreach (var p in i.personas) { first_persona = p; break; } if (i.full_name == this._persona_fullname_1 && this._persona_iid_1 == "") { this._persona_iid_1 = first_persona.iid; this._personas.add (first_persona); } else if (i.full_name == this._persona_fullname_2 && this._persona_iid_2 == "") { this._persona_iid_2 = first_persona.iid; this._personas.add (first_persona); } else if (i.personas.size > 1) { /* Lets check if it contains all the linking properties */ foreach (var proto in i.im_addresses.get_keys ()) { var addrs = i.im_addresses.get (proto); foreach (var a in addrs) { if (a.value == this._linking_props.get ("prop1")) { this._linking_props.unset ("prop1"); } else if (a.value == this._linking_props.get ("prop2")) { this._linking_props.unset ("prop2"); } } } if (this._linking_props.size == 0) { this._main_loop.quit (); } } /* We can try linking the personas only once we've got the * 2 initially created personas. */ if (this._personas.size == 2 && this._linking_fired == false) { this._linking_fired = true; /* FIXME: we need a way to sync with Tracker * delayed events. */ Timeout.add_seconds (2, () => { this._aggregator.link_personas.begin (this._personas); return false; }); } } } public int main (string[] args) { Test.init (ref args); var tests = new LinkPersonasTests (); tests.register (); Test.run (); tests.final_tear_down (); return 0; }