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