1 /* Test TpGroupMixin
2  *
3  * Copyright (C) 2007-2008 Collabora Ltd. <http://www.collabora.co.uk/>
4  * Copyright (C) 2007-2008 Nokia Corporation
5  *
6  * Copying and distribution of this file, with or without modification,
7  * are permitted in any medium without royalty provided the copyright
8  * notice and this notice are preserved.
9  */
10 
11 #include "config.h"
12 
13 #include <telepathy-glib/channel.h>
14 #include <telepathy-glib/connection.h>
15 #include <telepathy-glib/dbus.h>
16 #include <telepathy-glib/debug.h>
17 #include <telepathy-glib/interfaces.h>
18 #include <telepathy-glib/gtypes.h>
19 #include <telepathy-glib/proxy-subclass.h>
20 
21 #include "tests/lib/myassert.h"
22 #include "tests/lib/simple-conn.h"
23 #include "tests/lib/textchan-group.h"
24 #include "tests/lib/util.h"
25 
26 #define IDENTIFIER "them@example.org"
27 
28 static GMainLoop *mainloop;
29 TpTestsTextChannelGroup *service_chan;
30 TpChannel *chan = NULL;
31 TpHandleRepoIface *contact_repo;
32 TpHandle self_handle, camel, camel2;
33 
34 typedef void (*diff_checker) (const GArray *added, const GArray *removed,
35     const GArray *local_pending, const GArray *remote_pending,
36     const GHashTable *details);
37 
38 static gboolean expecting_members_changed = FALSE;
39 static gboolean expecting_members_changed_detailed = FALSE;
40 static const gchar *expected_message;
41 static TpHandle expected_actor;
42 static TpChannelGroupChangeReason expected_reason;
43 static diff_checker expected_diffs;
44 
45 static void
expect_signals(const gchar * message,TpHandle actor,TpChannelGroupChangeReason reason,diff_checker check_diffs)46 expect_signals (const gchar *message,
47                 TpHandle actor,
48                 TpChannelGroupChangeReason reason,
49                 diff_checker check_diffs)
50 {
51   expecting_members_changed = TRUE;
52   expecting_members_changed_detailed = TRUE;
53 
54   expected_message = message;
55   expected_actor = actor;
56   expected_reason = reason;
57   expected_diffs = check_diffs;
58 }
59 
60 static gboolean
outstanding_signals(void)61 outstanding_signals (void)
62 {
63   return (expecting_members_changed || expecting_members_changed_detailed);
64 }
65 
66 static void
wait_for_outstanding_signals(void)67 wait_for_outstanding_signals (void)
68 {
69   if (outstanding_signals ())
70     g_main_loop_run (mainloop);
71 }
72 
73 static void
on_members_changed(TpChannel * proxy,const gchar * arg_Message,const GArray * arg_Added,const GArray * arg_Removed,const GArray * arg_Local_Pending,const GArray * arg_Remote_Pending,guint arg_Actor,guint arg_Reason,gpointer user_data,GObject * weak_object)74 on_members_changed (TpChannel *proxy,
75                     const gchar *arg_Message,
76                     const GArray *arg_Added,
77                     const GArray *arg_Removed,
78                     const GArray *arg_Local_Pending,
79                     const GArray *arg_Remote_Pending,
80                     guint arg_Actor,
81                     guint arg_Reason,
82                     gpointer user_data,
83                     GObject *weak_object)
84 {
85   MYASSERT (expecting_members_changed, ": got unexpected MembersChanged");
86   expecting_members_changed = FALSE;
87 
88   g_assert_cmpstr (arg_Message, ==, expected_message);
89   g_assert_cmpuint (arg_Actor, ==, expected_actor);
90   g_assert_cmpuint (arg_Reason, ==, expected_reason);
91 
92   expected_diffs (arg_Added, arg_Removed, arg_Local_Pending,
93       arg_Remote_Pending, NULL);
94 
95   if (!outstanding_signals ())
96     g_main_loop_quit (mainloop);
97 }
98 
99 static void
on_members_changed_detailed(TpChannel * proxy,const GArray * arg_Added,const GArray * arg_Removed,const GArray * arg_Local_Pending,const GArray * arg_Remote_Pending,GHashTable * arg_Details,gpointer user_data,GObject * weak_object)100 on_members_changed_detailed (TpChannel *proxy,
101                              const GArray *arg_Added,
102                              const GArray *arg_Removed,
103                              const GArray *arg_Local_Pending,
104                              const GArray *arg_Remote_Pending,
105                              GHashTable *arg_Details,
106                              gpointer user_data,
107                              GObject *weak_object)
108 {
109   const gchar *message;
110   TpHandle actor;
111   TpChannelGroupChangeReason reason;
112   gboolean valid;
113 
114   MYASSERT (expecting_members_changed_detailed,
115       ": got unexpected MembersChangedDetailed");
116   expecting_members_changed_detailed = FALSE;
117 
118   message = tp_asv_get_string (arg_Details, "message");
119 
120   if (message == NULL)
121     message = "";
122 
123   g_assert_cmpstr (message, ==, expected_message);
124 
125   actor = tp_asv_get_uint32 (arg_Details, "actor", &valid);
126   if (valid)
127     {
128       g_assert_cmpuint (actor, ==, expected_actor);
129     }
130   else
131     {
132       g_assert_cmpuint (expected_actor, ==, 0);
133       MYASSERT (tp_asv_lookup (arg_Details, "actor") == NULL,
134           ": wanted an actor, not an imposter");
135     }
136 
137   reason = tp_asv_get_uint32 (arg_Details, "change-reason", &valid);
138   if (valid)
139     {
140       g_assert_cmpuint (reason, ==, expected_reason);
141     }
142   else
143     {
144       g_assert_cmpuint (expected_reason, ==,
145           TP_CHANNEL_GROUP_CHANGE_REASON_NONE);
146       MYASSERT (tp_asv_lookup (arg_Details, "reason") == NULL,
147           ": utterly unreasonable");
148     }
149 
150   expected_diffs (arg_Added, arg_Removed, arg_Local_Pending,
151       arg_Remote_Pending, arg_Details);
152 
153   if (!outstanding_signals ())
154     g_main_loop_quit (mainloop);
155 }
156 
157 static void
check_initial_properties(void)158 check_initial_properties (void)
159 {
160   GHashTable *props = NULL;
161   GArray *members;
162   gboolean valid;
163   GError *error = NULL;
164   TpChannelGroupFlags flags;
165 
166   MYASSERT (tp_cli_dbus_properties_run_get_all (chan, -1,
167       TP_IFACE_CHANNEL_INTERFACE_GROUP, &props, &error, NULL), "");
168   g_assert_no_error (error);
169 
170   members = tp_asv_get_boxed (props, "Members", DBUS_TYPE_G_UINT_ARRAY);
171   MYASSERT (members != NULL, ": Members should be defined"); \
172   MYASSERT (members->len == 0, ": Members should be empty initally");
173 
174   members = tp_asv_get_boxed (props, "RemotePendingMembers",
175       DBUS_TYPE_G_UINT_ARRAY);
176   MYASSERT (members != NULL, ": RemotePendingMembers should be defined"); \
177   MYASSERT (members->len == 0, ": RemotePendingMembers should be empty initally");
178 
179   members = tp_asv_get_boxed (props, "LocalPendingMembers",
180       TP_ARRAY_TYPE_LOCAL_PENDING_INFO_LIST);
181   MYASSERT (members != NULL, ": LocalPendingMembers should be defined"); \
182   MYASSERT (members->len == 0, ": LocalPendingMembers should be empty initally");
183 
184   tp_asv_get_uint32 (props, "SelfHandle", &valid);
185   MYASSERT (valid, ": SelfHandle property should be defined");
186 
187   flags = tp_asv_get_uint32 (props, "GroupFlags", &valid);
188   MYASSERT (flags, ": GroupFlags property should be defined");
189   g_assert_cmpuint (flags, ==,
190       TP_CHANNEL_GROUP_FLAG_PROPERTIES |
191       TP_CHANNEL_GROUP_FLAG_MEMBERS_CHANGED_DETAILED |
192       TP_CHANNEL_GROUP_FLAG_CAN_ADD);
193 
194   g_hash_table_unref (props);
195 }
196 
197 static void
details_contains_ids_for(const GHashTable * details,TpHandle * hs)198 details_contains_ids_for (const GHashTable *details,
199                           TpHandle *hs)
200 {
201   GHashTable *contact_ids;
202   const gchar *id;
203   guint n = 0;
204   TpHandle *h;
205 
206   if (details == NULL)
207     return;
208 
209   contact_ids = tp_asv_get_boxed (details, "contact-ids",
210       TP_HASH_TYPE_HANDLE_IDENTIFIER_MAP);
211   g_assert (contact_ids != NULL);
212 
213   for (h = hs; *h != 0; h++)
214     {
215       n++;
216 
217       id = g_hash_table_lookup (contact_ids, GUINT_TO_POINTER (*h));
218       MYASSERT (id != NULL, ": id for %u in map", *h);
219       g_assert_cmpstr (id, ==, tp_handle_inspect (contact_repo, *h));
220     }
221 
222   MYASSERT (g_hash_table_size (contact_ids) == n, ": %u contact IDs", n);
223 }
224 
225 static void
self_added_to_lp(const GArray * added,const GArray * removed,const GArray * local_pending,const GArray * remote_pending,const GHashTable * details)226 self_added_to_lp (const GArray *added,
227                   const GArray *removed,
228                   const GArray *local_pending,
229                   const GArray *remote_pending,
230                   const GHashTable *details)
231 {
232   TpHandle h;
233   TpHandle hs[] = { self_handle, 0 };
234 
235   MYASSERT (added->len == 0, ": no new added to members");
236   MYASSERT (removed->len == 0, ": no-one removed");
237   MYASSERT (remote_pending->len == 0, ": no new remote pending");
238   MYASSERT (local_pending->len == 1, ": one local pending...");
239 
240   /* ...which is us */
241   h = g_array_index (local_pending, TpHandle, 0);
242   g_assert_cmpuint (h, ==, self_handle);
243 
244   details_contains_ids_for (details, hs);
245 }
246 
247 static void
self_added_to_members(const GArray * added,const GArray * removed,const GArray * local_pending,const GArray * remote_pending,const GHashTable * details)248 self_added_to_members (const GArray *added,
249                        const GArray *removed,
250                        const GArray *local_pending,
251                        const GArray *remote_pending,
252                        const GHashTable *details)
253 {
254   TpHandle h;
255   TpHandle hs[] = { self_handle, 0 };
256 
257   MYASSERT (added->len == 1, ": one added");
258 
259   h = g_array_index (added, TpHandle, 0);
260   g_assert_cmpuint (h, ==, self_handle);
261 
262   MYASSERT (removed->len == 0, ": no-one removed");
263   MYASSERT (local_pending->len == 0, ": no new local pending");
264   MYASSERT (remote_pending->len == 0, ": no new remote pending");
265 
266   details_contains_ids_for (details, hs);
267 }
268 
269 static void
check_incoming_invitation(void)270 check_incoming_invitation (void)
271 {
272   GError *error = NULL;
273 
274   /* We get an invitation to the channel */
275   {
276     TpIntset *add_local_pending = tp_intset_new ();
277     tp_intset_add (add_local_pending, self_handle);
278 
279     expect_signals ("HELLO THAR", 0, TP_CHANNEL_GROUP_CHANGE_REASON_INVITED,
280         self_added_to_lp);
281     tp_group_mixin_change_members ((GObject *) service_chan, "HELLO THAR", NULL,
282         NULL, add_local_pending, NULL, 0,
283         TP_CHANNEL_GROUP_CHANGE_REASON_INVITED);
284     wait_for_outstanding_signals ();
285     MYASSERT (!outstanding_signals (),
286         ": MembersChanged and MembersChangedDetailed should have fired once");
287 
288     tp_intset_destroy (add_local_pending);
289   }
290 
291   /* We accept the invitation; even though the channel lacks CanAdd we should
292    * be able to move someone from local pending to members by calling Add().
293    */
294   {
295     GArray *contacts = g_array_sized_new (FALSE, FALSE, sizeof (TpHandle), 1);
296     g_array_append_val (contacts, self_handle);
297 
298     expect_signals ("", self_handle, TP_CHANNEL_GROUP_CHANGE_REASON_NONE,
299         self_added_to_members);
300     MYASSERT (tp_cli_channel_interface_group_run_add_members (chan, -1,
301         contacts, "", &error, NULL), "");
302     g_assert_no_error (error);
303     wait_for_outstanding_signals ();
304     MYASSERT (!outstanding_signals (),
305         ": MembersChanged and MembersChangedDetailed should have fired once");
306 
307     g_array_unref (contacts);
308   }
309 }
310 
311 static void
camel_added(const GArray * added,const GArray * removed,const GArray * local_pending,const GArray * remote_pending,const GHashTable * details)312 camel_added (const GArray *added,
313              const GArray *removed,
314              const GArray *local_pending,
315              const GArray *remote_pending,
316              const GHashTable *details)
317 {
318   TpHandle h;
319   TpHandle hs[] = { camel, 0 };
320 
321   MYASSERT (added->len == 1, ": one added");
322 
323   h = g_array_index (added, TpHandle, 0);
324   g_assert_cmpuint (h, ==, camel);
325 
326   details_contains_ids_for (details, hs);
327 
328   MYASSERT (removed->len == 0, ": no-one removed");
329   MYASSERT (local_pending->len == 0, ": no new local pending");
330   MYASSERT (remote_pending->len == 0, ": no new remote pending");
331 }
332 
333 static void
camel2_added(const GArray * added,const GArray * removed,const GArray * local_pending,const GArray * remote_pending,const GHashTable * details)334 camel2_added (const GArray *added,
335               const GArray *removed,
336               const GArray *local_pending,
337               const GArray *remote_pending,
338               const GHashTable *details)
339 {
340   TpHandle h;
341   /* camel is the actor */
342   TpHandle hs[] = { camel, camel2, 0 };
343 
344   MYASSERT (added->len == 1, ": one added");
345 
346   h = g_array_index (added, TpHandle, 0);
347   g_assert_cmpuint (h, ==, camel2);
348 
349   details_contains_ids_for (details, hs);
350 
351   MYASSERT (removed->len == 0, ": no-one removed");
352   MYASSERT (local_pending->len == 0, ": no new local pending");
353   MYASSERT (remote_pending->len == 0, ": no new remote pending");
354 }
355 
356 static void
camel_removed(const GArray * added,const GArray * removed,const GArray * local_pending,const GArray * remote_pending,const GHashTable * details)357 camel_removed (const GArray *added,
358                const GArray *removed,
359                const GArray *local_pending,
360                const GArray *remote_pending,
361                const GHashTable *details)
362 {
363   TpHandle h;
364   /* camel2 is the actor. camel shouldn't be in the ids, because they were
365    * removed and the spec says that you can leave those out, and we want
366    * tp-glib's automatic construction of contact-ids to work in the #ubuntu
367    * case.
368    */
369   TpHandle hs[] = { camel2, 0 };
370 
371   MYASSERT (removed->len == 1, ": one removed");
372 
373   h = g_array_index (removed, TpHandle, 0);
374   g_assert_cmpuint (h, ==, camel);
375 
376   MYASSERT (added->len == 0, ": no-one added");
377   MYASSERT (local_pending->len == 0, ": no new local pending");
378   MYASSERT (remote_pending->len == 0, ": no new remote pending");
379 
380   details_contains_ids_for (details, hs);
381 }
382 
383 static void
in_the_desert(void)384 in_the_desert (void)
385 {
386   camel  = tp_handle_ensure (contact_repo, "camel", NULL, NULL);
387   camel2 = tp_handle_ensure (contact_repo, "camel2", NULL, NULL);
388 
389   /* A camel is approaching */
390   {
391     TpIntset *add = tp_intset_new ();
392 
393     tp_intset_add (add, camel);
394     expect_signals ("", camel, TP_CHANNEL_GROUP_CHANGE_REASON_NONE,
395         camel_added);
396     tp_group_mixin_change_members ((GObject *) service_chan, NULL, add, NULL,
397         NULL, NULL, camel, TP_CHANNEL_GROUP_CHANGE_REASON_NONE);
398     wait_for_outstanding_signals ();
399     MYASSERT (!outstanding_signals (),
400         ": MembersChanged and MembersChangedDetailed should have fired once");
401 
402     tp_intset_destroy (add);
403   }
404 
405   /* A second camel is approaching (invited by the first camel) */
406   {
407     TpIntset *add = tp_intset_new ();
408     GHashTable *details = g_hash_table_new_full (g_str_hash, g_str_equal,
409         NULL, (GDestroyNotify) tp_g_value_slice_free);
410 
411     tp_intset_add (add, camel2);
412 
413     g_hash_table_insert (details, "actor", tp_g_value_slice_new_uint (camel));
414 
415     expect_signals ("", camel, TP_CHANNEL_GROUP_CHANGE_REASON_NONE,
416         camel2_added);
417     tp_group_mixin_change_members_detailed ((GObject *) service_chan, add,
418         NULL, NULL, NULL, details);
419     wait_for_outstanding_signals ();
420     MYASSERT (!outstanding_signals (),
421         ": MembersChanged and MembersChangedDetailed should have fired once");
422 
423     tp_intset_destroy (add);
424     g_hash_table_unref (details);
425   }
426 
427   {
428     TpIntset *del = tp_intset_new ();
429     GHashTable *details = g_hash_table_new_full (g_str_hash, g_str_equal,
430         NULL, (GDestroyNotify) tp_g_value_slice_free);
431 
432     tp_intset_add (del, camel);
433 
434     g_hash_table_insert (details, "actor", tp_g_value_slice_new_uint (camel2));
435 
436     /* It turns out that spitting was not included in the GroupChangeReason
437      * enum.
438      */
439     g_hash_table_insert (details, "error",
440         tp_g_value_slice_new_static_string ("le.mac.Spat"));
441     g_hash_table_insert (details, "saliva-consistency",
442         tp_g_value_slice_new_static_string ("fluid"));
443 
444     /* Kicking is the closest we have to this .. unsavory act. */
445     g_hash_table_insert (details, "change-reason",
446         tp_g_value_slice_new_uint (TP_CHANNEL_GROUP_CHANGE_REASON_KICKED));
447     g_hash_table_insert (details, "message",
448         tp_g_value_slice_new_static_string ("*ptooey*"));
449 
450     /* Check that all the right information was extracted from the dict. */
451     expect_signals ("*ptooey*", camel2,
452         TP_CHANNEL_GROUP_CHANGE_REASON_KICKED, camel_removed);
453     tp_group_mixin_change_members_detailed ((GObject *) service_chan, NULL,
454         del, NULL, NULL, details);
455     wait_for_outstanding_signals ();
456     MYASSERT (!outstanding_signals (),
457         ": MembersChanged and MembersChangedDetailed should have fired once");
458 
459     tp_intset_destroy (del);
460     g_hash_table_unref (details);
461   }
462 
463   /* We and the second camel should be left in the channel */
464   {
465     const TpIntset *members = tp_channel_group_get_members (chan);
466     GArray *service_members;
467     TpHandle a, b;
468 
469     g_assert_cmpuint (tp_intset_size (members), ==, 2);
470     MYASSERT (tp_intset_is_member (members, self_handle), "");
471     MYASSERT (tp_intset_is_member (members, camel2), ": what a pity");
472 
473     /* And let's check that the group mixin agrees, in case that's just the
474      * client binding being wrong.
475      */
476     tp_group_mixin_get_members ((GObject *) service_chan, &service_members,
477         NULL);
478     g_assert_cmpuint (service_members->len, ==, 2);
479     a = g_array_index (service_members, TpHandle, 0);
480     b = g_array_index (service_members, TpHandle, 1);
481     MYASSERT (a != b, "");
482     MYASSERT (a == self_handle || b == self_handle, "");
483     MYASSERT (a == camel2 || b == camel2, "");
484 
485     g_array_unref (service_members);
486   }
487 
488   tp_handle_unref (contact_repo, camel);
489   tp_handle_unref (contact_repo, camel2);
490 }
491 
492 static void
test_group_mixin(void)493 test_group_mixin (void)
494 {
495   GError *error = NULL;
496 
497   MYASSERT (tp_channel_run_until_ready (chan, &error, NULL), "");
498   g_assert_no_error (error);
499 
500   MYASSERT (tp_proxy_has_interface (chan, TP_IFACE_CHANNEL_INTERFACE_GROUP),
501       "");
502 
503   tp_cli_channel_interface_group_connect_to_members_changed (chan,
504       on_members_changed, NULL, NULL, NULL, NULL);
505   tp_cli_channel_interface_group_connect_to_members_changed_detailed (chan,
506       on_members_changed_detailed, NULL, NULL, NULL, NULL);
507 
508   check_initial_properties ();
509 
510   check_incoming_invitation ();
511 
512   in_the_desert ();
513 }
514 
515 int
main(int argc,char ** argv)516 main (int argc,
517       char **argv)
518 {
519   TpTestsSimpleConnection *service_conn;
520   TpBaseConnection *service_conn_as_base;
521   TpDBusDaemon *dbus;
522   TpConnection *conn;
523   GError *error = NULL;
524   gchar *name;
525   gchar *conn_path;
526   gchar *chan_path;
527 
528   tp_tests_abort_after (10);
529   tp_debug_set_flags ("all");
530   dbus = tp_tests_dbus_daemon_dup_or_die ();
531 
532   service_conn = TP_TESTS_SIMPLE_CONNECTION (tp_tests_object_new_static_class (
533         TP_TESTS_TYPE_SIMPLE_CONNECTION,
534         "account", "me@example.com",
535         "protocol", "simple",
536         NULL));
537   service_conn_as_base = TP_BASE_CONNECTION (service_conn);
538   MYASSERT (service_conn != NULL, "");
539   MYASSERT (service_conn_as_base != NULL, "");
540 
541   MYASSERT (tp_base_connection_register (service_conn_as_base, "simple",
542         &name, &conn_path, &error), "");
543   g_assert_no_error (error);
544 
545   conn = tp_connection_new (dbus, name, conn_path, &error);
546   MYASSERT (conn != NULL, "");
547   g_assert_no_error (error);
548 
549   MYASSERT (tp_connection_run_until_ready (conn, TRUE, &error, NULL),
550       "");
551   g_assert_no_error (error);
552 
553   contact_repo = tp_base_connection_get_handles (service_conn_as_base,
554       TP_HANDLE_TYPE_CONTACT);
555   MYASSERT (contact_repo != NULL, "");
556   self_handle = tp_handle_ensure (contact_repo, "me@example.com", NULL, NULL);
557 
558   chan_path = g_strdup_printf ("%s/Channel", conn_path);
559 
560   service_chan = TP_TESTS_TEXT_CHANNEL_GROUP (
561       tp_tests_object_new_static_class (
562         TP_TESTS_TYPE_TEXT_CHANNEL_GROUP,
563         "connection", service_conn,
564         "object-path", chan_path,
565         "detailed", TRUE,
566         NULL));
567 
568   mainloop = g_main_loop_new (NULL, FALSE);
569 
570   MYASSERT (tp_cli_connection_run_connect (conn, -1, &error, NULL), "");
571   g_assert_no_error (error);
572 
573   chan = tp_channel_new (conn, chan_path, NULL, TP_UNKNOWN_HANDLE_TYPE, 0,
574       &error);
575   g_assert_no_error (error);
576 
577   MYASSERT (tp_channel_run_until_ready (chan, &error, NULL), "");
578   g_assert_no_error (error);
579 
580   test_group_mixin ();
581 
582   tp_tests_connection_assert_disconnect_succeeds (conn);
583 
584   /* clean up */
585 
586   g_object_unref (chan);
587   g_main_loop_unref (mainloop);
588   mainloop = NULL;
589 
590   g_object_unref (conn);
591   g_object_unref (service_chan);
592 
593   service_conn_as_base = NULL;
594   g_object_unref (service_conn);
595   g_object_unref (dbus);
596   g_free (name);
597   g_free (conn_path);
598   g_free (chan_path);
599 
600   return 0;
601 }
602