/* Feature test for TpContact creation. * * Code missing coverage in contact.c: * - connection becoming invalid * - fatal error on the connection * - inconsistent CM * - having to fall back to RequestAliases * - get_contacts_by_id with features (but it's trivial) * * Copyright © 2008–2011 Collabora Ltd. * Copyright © 2008 Nokia Corporation * Copyright © 2007 Will Thompson * * Copying and distribution of this file, with or without modification, * are permitted in any medium without royalty provided the copyright * notice and this notice are preserved. */ #include "config.h" #include #include #include #include #include #include #include #include #include #include #include "tests/lib/contacts-conn.h" #include "tests/lib/broken-client-types-conn.h" #include "tests/lib/debug.h" #include "tests/lib/myassert.h" #include "tests/lib/util.h" #define MEMBERS_CHANGED_MATCH_RULE \ "type='signal'," \ "interface='" TP_IFACE_CHANNEL_INTERFACE_GROUP "'," \ "member='MembersChanged'" typedef struct { GMainLoop *loop; GError *error /* initialized to 0 */; GPtrArray *contacts; GArray *invalid; gchar **good_ids; GHashTable *bad_ids; } Result; typedef struct { Result result; TpBaseConnection *base_connection; TpBaseConnection *legacy_base_connection; TpBaseConnection *no_requests_base_connection; TpTestsContactsConnection *service_conn; TpHandleRepoIface *service_repo; TpConnection *client_conn; TpConnection *legacy_client_conn; TpConnection *no_requests_client_conn; } Fixture; /* We only really actively test TP_CONTACT_FEATURE_ALIAS, but preparing any * of these once should be enough, assuming that the CM is not broken. */ static TpContactFeature all_contact_features[] = { TP_CONTACT_FEATURE_ALIAS, TP_CONTACT_FEATURE_AVATAR_TOKEN, TP_CONTACT_FEATURE_PRESENCE, TP_CONTACT_FEATURE_LOCATION, TP_CONTACT_FEATURE_CAPABILITIES, TP_CONTACT_FEATURE_AVATAR_DATA, TP_CONTACT_FEATURE_CONTACT_INFO, TP_CONTACT_FEATURE_CLIENT_TYPES, TP_CONTACT_FEATURE_SUBSCRIPTION_STATES, TP_CONTACT_FEATURE_CONTACT_GROUPS, TP_CONTACT_FEATURE_CONTACT_BLOCKING }; /* If people add new features, they should add them to this test. We could * generate the list dynamically but this seems less brittle. */ G_STATIC_ASSERT (G_N_ELEMENTS (all_contact_features) == TP_NUM_CONTACT_FEATURES); static void by_handle_cb (TpConnection *connection, guint n_contacts, TpContact * const *contacts, guint n_invalid, const TpHandle *invalid, const GError *error, gpointer user_data, GObject *weak_object) { Result *result = user_data; g_assert (result->invalid == NULL); g_assert (result->contacts == NULL); g_assert (result->error == NULL); g_assert (result->good_ids == NULL); g_assert (result->bad_ids == NULL); if (error == NULL) { guint i; DEBUG ("got %u contacts and %u invalid", n_contacts, n_invalid); result->invalid = g_array_sized_new (FALSE, FALSE, sizeof (TpHandle), n_invalid); g_array_append_vals (result->invalid, invalid, n_invalid); result->contacts = g_ptr_array_sized_new (n_contacts); for (i = 0; i < n_contacts; i++) { TpContact *contact = contacts[i]; GFile *avatar_file; gchar *avatar_uri = NULL; avatar_file = tp_contact_get_avatar_file (contact); if (avatar_file != NULL) avatar_uri = g_file_get_uri (avatar_file); DEBUG ("contact #%u: %p", i, contact); DEBUG ("contact #%u alias: %s", i, tp_contact_get_alias (contact)); DEBUG ("contact #%u avatar token: %s", i, tp_contact_get_avatar_token (contact)); DEBUG ("contact #%u avatar MIME type: %s", i, tp_contact_get_avatar_mime_type (contact)); DEBUG ("contact #%u avatar file: %s", i, avatar_uri); DEBUG ("contact #%u presence type: %u", i, tp_contact_get_presence_type (contact)); DEBUG ("contact #%u presence status: %s", i, tp_contact_get_presence_status (contact)); DEBUG ("contact #%u presence message: %s", i, tp_contact_get_presence_message (contact)); g_ptr_array_add (result->contacts, g_object_ref (contact)); g_free (avatar_uri); } } else { DEBUG ("got an error: %s %u: %s", g_quark_to_string (error->domain), error->code, error->message); result->error = g_error_copy (error); } } static void finish (gpointer r) { Result *result = r; g_main_loop_quit (result->loop); } static void reset_result (Result *result) { tp_clear_pointer (&result->invalid, g_array_unref); if (result->contacts != NULL) g_ptr_array_foreach (result->contacts, (GFunc) g_object_unref, NULL); tp_clear_pointer (&result->contacts, g_ptr_array_unref); tp_clear_pointer (&result->good_ids, g_strfreev); tp_clear_pointer (&result->bad_ids, g_hash_table_unref); g_clear_error (&result->error); } static void contact_info_verify (TpContact *contact) { GList *info; TpContactInfoField *field; g_assert (tp_contact_has_feature (contact, TP_CONTACT_FEATURE_CONTACT_INFO)); info = tp_contact_get_contact_info (contact); g_assert (info != NULL); g_assert (info->data != NULL); g_assert (info->next == NULL); field = info->data; g_assert_cmpstr (field->field_name, ==, "n"); g_assert (field->parameters != NULL); g_assert (field->parameters[0] == NULL); g_assert (field->field_value != NULL); g_assert_cmpstr (field->field_value[0], ==, "Foo"); g_assert (field->field_value[1] == NULL); g_list_free (info); } static void contact_info_notify_cb (TpContact *contact, GParamSpec *pspec, Result *result) { contact_info_verify (contact); finish (result); } static void contact_info_prepare_cb (GObject *object, GAsyncResult *res, gpointer user_data) { TpConnection *connection = TP_CONNECTION (object); Result *result = user_data; if (tp_proxy_prepare_finish (connection, res, &result->error)) { TpContactInfoFlags flags; GList *specs, *l; flags = tp_connection_get_contact_info_flags (connection); g_assert_cmpint (flags, ==, TP_CONTACT_INFO_FLAG_PUSH | TP_CONTACT_INFO_FLAG_CAN_SET); specs = tp_connection_get_contact_info_supported_fields (connection); g_assert_cmpuint (g_list_length (specs), ==, 5); for (l = specs; l != NULL; l = l->next) { TpContactInfoFieldSpec *spec = l->data; if (!tp_strdiff (spec->name, "bday") || !tp_strdiff (spec->name, "fn")) { g_assert (spec->parameters != NULL); g_assert (spec->parameters[0] == NULL); g_assert_cmpint (spec->flags, ==, 0); g_assert_cmpint (spec->max, ==, 1); } else if (!tp_strdiff (spec->name, "email") || !tp_strdiff (spec->name, "tel") || !tp_strdiff (spec->name, "url")) { g_assert (spec->parameters != NULL); g_assert (spec->parameters[0] == NULL); g_assert_cmpint (spec->flags, ==, 0); g_assert_cmpint (spec->max, ==, G_MAXUINT32); } else { g_assert_not_reached (); } } g_list_free (specs); } finish (result); } static void contact_info_set_cb (GObject *object, GAsyncResult *res, gpointer user_data) { TpConnection *connection = TP_CONNECTION (object); Result *result = user_data; tp_connection_set_contact_info_finish (connection, res, &result->error); finish (result); } static void contact_info_request_cb (GObject *object, GAsyncResult *res, gpointer user_data) { TpContact *contact = TP_CONTACT (object); Result *result = user_data; contact_info_verify (contact); tp_contact_request_contact_info_finish (contact, res, &result->error); finish (result); } static void contact_info_request_cancelled_cb (GObject *object, GAsyncResult *res, gpointer user_data) { TpContact *contact = TP_CONTACT (object); Result *result = user_data; GError *error = NULL; tp_contact_request_contact_info_finish (contact, res, &error); g_assert_error (error, G_IO_ERROR, G_IO_ERROR_CANCELLED); g_clear_error (&error); finish (result); } static gboolean contact_info_request_cancel (gpointer cancellable) { g_cancellable_cancel (cancellable); return FALSE; } static void test_contact_info (Fixture *f, gconstpointer unused G_GNUC_UNUSED) { TpTestsContactsConnection *service_conn = f->service_conn; TpConnection *client_conn = f->client_conn; Result result = { g_main_loop_new (NULL, FALSE), NULL, NULL, NULL }; TpHandleRepoIface *service_repo = tp_base_connection_get_handles ( (TpBaseConnection *) service_conn, TP_HANDLE_TYPE_CONTACT); TpContactFeature features[] = { TP_CONTACT_FEATURE_CONTACT_INFO }; TpContact *contact; TpHandle handle; const gchar *field_value[] = { "Foo", NULL }; GPtrArray *info; GList *info_list = NULL; GQuark conn_features[] = { TP_CONNECTION_FEATURE_CONTACT_INFO, 0 }; GCancellable *cancellable; /* Create fake info fields */ info = g_ptr_array_new_with_free_func ((GDestroyNotify) tp_value_array_free); g_ptr_array_add (info, tp_value_array_build (3, G_TYPE_STRING, "n", G_TYPE_STRV, NULL, G_TYPE_STRV, field_value, G_TYPE_INVALID)); info_list = g_list_prepend (info_list, tp_contact_info_field_new ("n", NULL, (GStrv) field_value)); tp_tests_contacts_connection_set_default_contact_info (service_conn, info); /* TEST1: Verify ContactInfo properties are correctly introspected on * TpConnection */ tp_proxy_prepare_async (client_conn, conn_features, contact_info_prepare_cb, &result); g_main_loop_run (result.loop); g_assert_no_error (result.error); /* TEST2: Set contact info on the connection, then get the self TpContact. * This tests the set operation works correctly and also test TpContact * correctly introspects the ContactInfo when the feature is requested. */ /* ... but first, get the SelfHandle contact without any features (regression * test for a related bug, fd.o #32191) */ handle = tp_connection_get_self_handle (client_conn); tp_connection_get_contacts_by_handle (client_conn, 1, &handle, 0, NULL, by_handle_cb, &result, finish, NULL); g_main_loop_run (result.loop); g_assert_no_error (result.error); reset_result (&result); tp_connection_set_contact_info_async (client_conn, info_list, contact_info_set_cb, &result); g_main_loop_run (result.loop); g_assert_no_error (result.error); handle = tp_connection_get_self_handle (client_conn); tp_connection_get_contacts_by_handle (client_conn, 1, &handle, G_N_ELEMENTS (features), features, by_handle_cb, &result, finish, NULL); g_main_loop_run (result.loop); g_assert_no_error (result.error); contact = g_ptr_array_index (result.contacts, 0); contact_info_verify (contact); reset_result (&result); /* TEST3: Create a TpContact with the INFO feature. Then change its info in * the CM. That should emit "notify::info" signal on the TpContact. */ handle = tp_handle_ensure (service_repo, "info-test-3", NULL, NULL); tp_connection_get_contacts_by_handle (client_conn, 1, &handle, G_N_ELEMENTS (features), features, by_handle_cb, &result, finish, NULL); g_main_loop_run (result.loop); g_assert_no_error (result.error); contact = g_ptr_array_index (result.contacts, 0); g_signal_connect (contact, "notify::contact-info", G_CALLBACK (contact_info_notify_cb), &result); tp_tests_contacts_connection_change_contact_info (service_conn, handle, info); g_main_loop_run (result.loop); g_assert_no_error (result.error); reset_result (&result); tp_handle_unref (service_repo, handle); /* TEST 4: First set the info in the CM for an handle, then create a TpContact * without INFO feature, and finally refresh the contact's info. */ handle = tp_handle_ensure (service_repo, "info-test-4", NULL, NULL); tp_tests_contacts_connection_change_contact_info (service_conn, handle, info); tp_connection_get_contacts_by_handle (client_conn, 1, &handle, 0, NULL, by_handle_cb, &result, finish, NULL); g_main_loop_run (result.loop); g_assert_no_error (result.error); contact = g_ptr_array_index (result.contacts, 0); g_assert (tp_contact_get_contact_info (contact) == NULL); g_signal_connect (contact, "notify::contact-info", G_CALLBACK (contact_info_notify_cb), &result); tp_connection_refresh_contact_info (client_conn, 1, &contact); g_main_loop_run (result.loop); g_assert_no_error (result.error); reset_result (&result); tp_handle_unref (service_repo, handle); /* TEST5: Create a TpContact without INFO feature, then request the contact's * info. */ handle = tp_handle_ensure (service_repo, "info-test-5", NULL, NULL); tp_connection_get_contacts_by_handle (client_conn, 1, &handle, 0, NULL, by_handle_cb, &result, finish, NULL); g_main_loop_run (result.loop); g_assert_no_error (result.error); contact = g_ptr_array_index (result.contacts, 0); g_assert (tp_contact_get_contact_info (contact) == NULL); tp_contact_request_contact_info_async (contact, NULL, contact_info_request_cb, &result); g_main_loop_run (result.loop); g_assert_no_error (result.error); reset_result (&result); tp_handle_unref (service_repo, handle); /* TEST6: Create a TpContact without INFO feature, then request the contact's * info, and cancel the request. */ handle = tp_handle_ensure (service_repo, "info-test-6", NULL, NULL); tp_connection_get_contacts_by_handle (client_conn, 1, &handle, 0, NULL, by_handle_cb, &result, finish, NULL); g_main_loop_run (result.loop); g_assert_no_error (result.error); contact = g_ptr_array_index (result.contacts, 0); g_assert (tp_contact_get_contact_info (contact) == NULL); cancellable = g_cancellable_new (); tp_contact_request_contact_info_async (contact, cancellable, contact_info_request_cancelled_cb, &result); g_idle_add_full (G_PRIORITY_HIGH, contact_info_request_cancel, cancellable, g_object_unref); g_main_loop_run (result.loop); g_assert_no_error (result.error); reset_result (&result); tp_handle_unref (service_repo, handle); /* Cleanup */ g_main_loop_unref (result.loop); g_ptr_array_unref (info); tp_contact_info_list_free (info_list); } static void prepare_avatar_requirements_cb (GObject *object, GAsyncResult *res, gpointer user_data) { TpConnection *connection = TP_CONNECTION (object); Result *result = user_data; if (tp_proxy_prepare_finish (connection, res, &result->error)) { TpAvatarRequirements *req; req = tp_connection_get_avatar_requirements (connection); g_assert (req != NULL); g_assert (req->supported_mime_types != NULL); g_assert_cmpstr (req->supported_mime_types[0], ==, "image/png"); g_assert (req->supported_mime_types[1] == NULL); g_assert_cmpuint (req->minimum_width, ==, 1); g_assert_cmpuint (req->minimum_height, ==, 2); g_assert_cmpuint (req->recommended_width, ==, 3); g_assert_cmpuint (req->recommended_height, ==, 4); g_assert_cmpuint (req->maximum_width, ==, 5); g_assert_cmpuint (req->maximum_height, ==, 6); g_assert_cmpuint (req->maximum_bytes, ==, 7); } finish (result); } static void test_avatar_requirements (Fixture *f, gconstpointer unused G_GNUC_UNUSED) { TpConnection *client_conn = f->client_conn; Result result = { g_main_loop_new (NULL, FALSE), NULL, NULL, NULL }; GQuark features[] = { TP_CONNECTION_FEATURE_AVATAR_REQUIREMENTS, 0 }; g_message (G_STRFUNC); tp_proxy_prepare_async (TP_PROXY (client_conn), features, prepare_avatar_requirements_cb, &result); g_main_loop_run (result.loop); g_assert_no_error (result.error); g_main_loop_unref (result.loop); } static TpContact * create_contact_with_fake_avatar (TpTestsContactsConnection *service_conn, TpConnection *client_conn, const gchar *id, gboolean request_avatar) { Result result = { g_main_loop_new (NULL, FALSE), NULL, NULL, NULL }; TpHandleRepoIface *service_repo = tp_base_connection_get_handles ( (TpBaseConnection *) service_conn, TP_HANDLE_TYPE_CONTACT); TpContactFeature feature; const gchar avatar_data[] = "fake-avatar-data"; const gchar avatar_token[] = "fake-avatar-token"; const gchar avatar_mime_type[] = "fake-avatar-mime-type"; TpContact *contact; TpHandle handle; GArray *array; gchar *content = NULL; handle = tp_handle_ensure (service_repo, id, NULL, NULL); array = g_array_new (FALSE, FALSE, sizeof (gchar)); g_array_append_vals (array, avatar_data, strlen (avatar_data) + 1); tp_tests_contacts_connection_change_avatar_data (service_conn, handle, array, avatar_mime_type, avatar_token); if (request_avatar) feature = TP_CONTACT_FEATURE_AVATAR_DATA; else feature = TP_CONTACT_FEATURE_AVATAR_TOKEN; tp_connection_get_contacts_by_handle (client_conn, 1, &handle, 1, &feature, by_handle_cb, &result, finish, NULL); g_main_loop_run (result.loop); g_assert_no_error (result.error); contact = g_object_ref (g_ptr_array_index (result.contacts, 0)); g_assert_cmpstr (tp_contact_get_avatar_token (contact), ==, avatar_token); if (request_avatar) { GFile *avatar_file; /* If we requested avatar, it could come later */ if (tp_contact_get_avatar_file (contact) == NULL) { g_signal_connect_swapped (contact, "notify::avatar-file", G_CALLBACK (finish), &result); g_main_loop_run (result.loop); } g_assert_cmpstr (tp_contact_get_avatar_mime_type (contact), ==, avatar_mime_type); avatar_file = tp_contact_get_avatar_file (contact); g_assert (avatar_file != NULL); g_file_load_contents (avatar_file, NULL, &content, NULL, NULL, &result.error); g_assert_no_error (result.error); g_assert_cmpstr (content, ==, avatar_data); g_free (content); } reset_result (&result); g_main_loop_unref (result.loop); tp_handle_unref (service_repo, handle); g_array_unref (array); return contact; } static void avatar_retrieved_cb (TpConnection *connection, guint handle, const gchar *token, const GArray *avatar, const gchar *mime_type, gpointer user_data, GObject *weak_object) { gboolean *called = user_data; *called = TRUE; } /* From telepathy-haze, with permission */ static gboolean haze_remove_directory (const gchar *path) { const gchar *child_path; GDir *dir = g_dir_open (path, 0, NULL); gboolean ret = TRUE; if (!dir) return FALSE; while (ret && (child_path = g_dir_read_name (dir))) { gchar *child_full_path = g_build_filename (path, child_path, NULL); if (g_file_test (child_full_path, G_FILE_TEST_IS_DIR)) { if (!haze_remove_directory (child_full_path)) ret = FALSE; } else { DEBUG ("deleting %s", child_full_path); if (g_unlink (child_full_path)) ret = FALSE; } g_free (child_full_path); } g_dir_close (dir); if (ret) { DEBUG ("deleting %s", path); ret = !g_rmdir (path); } return ret; } static void test_avatar_data (Fixture *f, gconstpointer unused G_GNUC_UNUSED) { TpTestsContactsConnection *service_conn = f->service_conn; TpConnection *client_conn = f->client_conn; gboolean avatar_retrieved_called; GError *error = NULL; TpContact *contact1, *contact2; TpProxySignalConnection *signal_id; g_message (G_STRFUNC); /* Check if AvatarRetrieved gets called */ signal_id = tp_cli_connection_interface_avatars_connect_to_avatar_retrieved ( client_conn, avatar_retrieved_cb, &avatar_retrieved_called, NULL, NULL, &error); g_assert_no_error (error); /* First time we create a contact, avatar should not be in cache, so * AvatarRetrived should be called */ avatar_retrieved_called = FALSE; contact1 = create_contact_with_fake_avatar (service_conn, client_conn, "fake-id1", TRUE); g_assert (avatar_retrieved_called); g_assert (contact1 != NULL); g_assert (tp_contact_get_avatar_file (contact1) != NULL); /* Second time we create a contact, avatar should be in cache now, so * AvatarRetrived should NOT be called */ avatar_retrieved_called = FALSE; contact2 = create_contact_with_fake_avatar (service_conn, client_conn, "fake-id2", TRUE); g_assert (!avatar_retrieved_called); g_assert (contact2 != NULL); g_assert (tp_contact_get_avatar_file (contact2) != NULL); g_assert (g_file_equal ( tp_contact_get_avatar_file (contact1), tp_contact_get_avatar_file (contact2))); tp_proxy_signal_connection_disconnect (signal_id); g_object_unref (contact1); g_object_unref (contact2); } static void test_avatar_data_after_token (Fixture *f, gconstpointer unused G_GNUC_UNUSED) { TpTestsContactsConnection *service_conn = f->service_conn; TpConnection *client_conn = f->client_conn; const gchar *id = "avatar-data-after-token"; TpContact *contact1, *contact2; g_message (G_STRFUNC); /* Create a contact with AVATAR_TOKEN feature */ contact1 = create_contact_with_fake_avatar (service_conn, client_conn, id, FALSE); g_assert (contact1 != NULL); g_assert (tp_contact_get_avatar_file (contact1) == NULL); /* Now create the same contact with AVATAR_DATA feature */ contact2 = create_contact_with_fake_avatar (service_conn, client_conn, id, TRUE); g_assert (contact2 != NULL); g_assert (tp_contact_get_avatar_file (contact2) != NULL); g_assert (contact1 == contact2); /* Cleanup */ g_object_unref (contact1); g_object_unref (contact2); } static void test_by_handle (Fixture *f, gconstpointer unused G_GNUC_UNUSED) { TpTestsContactsConnection *service_conn = f->service_conn; TpConnection *client_conn = f->client_conn; Result result = { g_main_loop_new (NULL, FALSE), NULL, NULL, NULL }; TpHandle handles[5] = { 0, 0, 0, 0, 0 }; TpHandleRepoIface *service_repo = tp_base_connection_get_handles ( (TpBaseConnection *) service_conn, TP_HANDLE_TYPE_CONTACT); TpContact *contacts[4]; gpointer weak_pointers[4]; guint i; g_message (G_STRFUNC); /* arrange for some handles to exist */ handles[0] = tp_handle_ensure (service_repo, "alice", NULL, NULL); MYASSERT (handles[0] != 0, ""); handles[1] = tp_handle_ensure (service_repo, "bob", NULL, NULL); MYASSERT (handles[1] != 0, ""); /* randomly guess at a handle that shouldn't exist */ handles[2] = 31337; MYASSERT (!tp_handle_is_valid (service_repo, 31337, NULL), ""); /* another valid handle */ handles[3] = tp_handle_ensure (service_repo, "chris", NULL, NULL); MYASSERT (handles[3] != 0, ""); /* another invalid handle */ handles[4] = 12345; MYASSERT (!tp_handle_is_valid (service_repo, 12345, NULL), ""); /* Make a request for the following 5 contacts: * - alice * - bob * - invalid handle 31337 * - chris * - invalid handle 12345 */ tp_connection_get_contacts_by_handle (client_conn, 5, handles, 0, NULL, by_handle_cb, &result, finish, NULL); g_main_loop_run (result.loop); MYASSERT (result.contacts->len == 3, ": %u", result.contacts->len); MYASSERT (result.invalid->len == 2, ": %u", result.invalid->len); g_assert_no_error (result.error); MYASSERT (g_ptr_array_index (result.contacts, 0) != NULL, ""); MYASSERT (g_ptr_array_index (result.contacts, 1) != NULL, ""); MYASSERT (g_ptr_array_index (result.contacts, 2) != NULL, ""); contacts[0] = g_object_ref (g_ptr_array_index (result.contacts, 0)); g_assert_cmpuint (tp_contact_get_handle (contacts[0]), ==, handles[0]); g_assert_cmpstr (tp_contact_get_identifier (contacts[0]), ==, "alice"); contacts[1] = g_object_ref (g_ptr_array_index (result.contacts, 1)); g_assert_cmpuint (tp_contact_get_handle (contacts[1]), ==, handles[1]); g_assert_cmpstr (tp_contact_get_identifier (contacts[1]), ==, "bob"); contacts[3] = g_object_ref (g_ptr_array_index (result.contacts, 2)); g_assert_cmpuint (tp_contact_get_handle (contacts[3]), ==, handles[3]); g_assert_cmpstr (tp_contact_get_identifier (contacts[3]), ==, "chris"); /* clean up before doing the second request */ reset_result (&result); g_assert (result.error == NULL); /* Replace one of the invalid handles with a valid one */ handles[2] = tp_handle_ensure (service_repo, "dora", NULL, NULL); MYASSERT (handles[2] != 0, ""); /* Make a request for the following 4 contacts: * - alice (TpContact exists) * - bob (TpContact exists) * - dora (TpContact needs to be created) * - chris (TpContact exists) */ tp_connection_get_contacts_by_handle (client_conn, 4, handles, 0, NULL, by_handle_cb, &result, finish, NULL); g_main_loop_run (result.loop); /* assert that we got the same contacts back */ MYASSERT (result.contacts->len == 4, ": %u", result.contacts->len); MYASSERT (result.invalid->len == 0, ": %u", result.invalid->len); g_assert_no_error (result.error); /* 0, 1 and 3 we already have a reference to */ MYASSERT (g_ptr_array_index (result.contacts, 0) == contacts[0], ""); g_object_unref (g_ptr_array_index (result.contacts, 0)); MYASSERT (g_ptr_array_index (result.contacts, 1) == contacts[1], ""); g_object_unref (g_ptr_array_index (result.contacts, 1)); MYASSERT (g_ptr_array_index (result.contacts, 3) == contacts[3], ""); g_object_unref (g_ptr_array_index (result.contacts, 3)); /* 2 we don't */ contacts[2] = g_ptr_array_index (result.contacts, 2); g_assert_cmpuint (tp_contact_get_handle (contacts[2]), ==, handles[2]); g_assert_cmpstr (tp_contact_get_identifier (contacts[2]), ==, "dora"); g_ptr_array_unref (result.contacts); result.contacts = NULL; /* clean up refs to contacts and assert that they aren't leaked */ for (i = 0; i < 4; i++) { weak_pointers[i] = contacts[i]; g_object_add_weak_pointer ((GObject *) contacts[i],weak_pointers +i); } for (i = 0; i < 4; i++) { g_object_unref (contacts[i]); MYASSERT (weak_pointers[i] == NULL, ": %u", i); } /* wait for ReleaseHandles to run */ tp_tests_proxy_run_until_dbus_queue_processed (client_conn); /* remaining cleanup */ g_assert (result.error == NULL); reset_result (&result); g_main_loop_unref (result.loop); } /* Silently removes the TpBaseConnection object from D-Bus, so that if the test * makes any D-Bus calls on it, it will fail (but the TpConnection proxy isn't * invalidated otherwise) */ static void make_the_connection_disappear (Fixture *f) { GError *error = NULL; gboolean ok; tp_dbus_daemon_unregister_object ( tp_base_connection_get_dbus_daemon (f->base_connection), f->base_connection); /* check that that worked */ ok = tp_cli_connection_run_get_self_handle (f->client_conn, -1, NULL, &error, NULL); g_assert_error (error, DBUS_GERROR, DBUS_GERROR_UNKNOWN_METHOD); g_assert (!ok); g_clear_error (&error); } /* Returns the TpBaseConnection to D-Bus (after a previous call to * make_the_connection_disappear()) */ static void put_the_connection_back (Fixture *f) { GError *error = NULL; gboolean ok; tp_dbus_daemon_register_object ( tp_base_connection_get_dbus_daemon (f->base_connection), tp_base_connection_get_object_path (f->base_connection), f->base_connection); /* check that *that* worked */ ok = tp_cli_connection_run_get_self_handle (f->client_conn, -1, NULL, &error, NULL); g_assert_no_error (error); g_assert (ok); } static TpHandle get_handle_with_no_caps (Fixture *f, const gchar *id) { TpHandle handle; GHashTable *capabilities; handle = tp_handle_ensure (f->service_repo, id, NULL, NULL); g_assert_cmpuint (handle, !=, 0); /* Unlike almost every other feature, with capabilities “not sure” and “none” * are different: you really might care about the difference between “I don't * know if blah can do video” versus “I know blah cannot do video”. * * It happens that we get the repeated-reintrospection behaviour for the * former case of contact caps. I can't really be bothered to fix this. */ capabilities = g_hash_table_new_full (NULL, NULL, NULL, (GDestroyNotify) g_ptr_array_unref); g_hash_table_insert (capabilities, GUINT_TO_POINTER (handle), g_ptr_array_new ()); tp_tests_contacts_connection_change_capabilities (f->service_conn, capabilities); g_hash_table_unref (capabilities); return handle; } static void test_by_handle_again (Fixture *f, gconstpointer unused G_GNUC_UNUSED) { Result result = { g_main_loop_new (NULL, FALSE), NULL, NULL, NULL }; TpHandle handle; TpContact *contact; gpointer weak_pointer; const gchar *alias = "Alice in Wonderland"; g_test_bug ("25181"); handle = get_handle_with_no_caps (f, "alice"); tp_tests_contacts_connection_change_aliases (f->service_conn, 1, &handle, &alias); tp_connection_get_contacts_by_handle (f->client_conn, 1, &handle, G_N_ELEMENTS (all_contact_features), all_contact_features, by_handle_cb, &result, finish, NULL); g_main_loop_run (result.loop); g_assert_cmpuint (result.contacts->len, ==, 1); g_assert_cmpuint (result.invalid->len, ==, 0); g_assert_no_error (result.error); g_assert (g_ptr_array_index (result.contacts, 0) != NULL); contact = g_object_ref (g_ptr_array_index (result.contacts, 0)); g_assert_cmpuint (tp_contact_get_handle (contact), ==, handle); g_assert_cmpstr (tp_contact_get_identifier (contact), ==, "alice"); g_assert_cmpstr (tp_contact_get_alias (contact), ==, "Alice in Wonderland"); /* clean up before doing the second request */ reset_result (&result); g_assert (result.error == NULL); make_the_connection_disappear (f); tp_connection_get_contacts_by_handle (f->client_conn, 1, &handle, G_N_ELEMENTS (all_contact_features), all_contact_features, by_handle_cb, &result, finish, NULL); g_main_loop_run (result.loop); g_assert_no_error (result.error); g_assert_cmpuint (result.contacts->len, ==, 1); g_assert_cmpuint (result.invalid->len, ==, 0); g_assert (g_ptr_array_index (result.contacts, 0) == contact); g_assert_cmpstr (tp_contact_get_alias (contact), ==, "Alice in Wonderland"); put_the_connection_back (f); g_assert (result.error == NULL); reset_result (&result); weak_pointer = contact; g_object_add_weak_pointer ((GObject *) contact, &weak_pointer); g_object_unref (contact); g_assert (weak_pointer == NULL); tp_tests_proxy_run_until_dbus_queue_processed (f->client_conn); g_main_loop_unref (result.loop); } static void test_by_handle_upgrade (Fixture *f, gconstpointer unused G_GNUC_UNUSED) { Result result = { g_main_loop_new (NULL, FALSE), NULL, NULL, NULL }; TpHandle handle; TpHandleRepoIface *service_repo = tp_base_connection_get_handles ( f->base_connection, TP_HANDLE_TYPE_CONTACT); TpContact *contact; gpointer weak_pointer; const gchar *alias = "Alice in Wonderland"; TpContactFeature feature = TP_CONTACT_FEATURE_ALIAS; g_test_bug ("32191"); handle = tp_handle_ensure (service_repo, "alice", NULL, NULL); g_assert_cmpuint (handle, !=, 0); tp_tests_contacts_connection_change_aliases (f->service_conn, 1, &handle, &alias); tp_connection_get_contacts_by_handle (f->client_conn, 1, &handle, 0, NULL, by_handle_cb, &result, finish, NULL); g_main_loop_run (result.loop); g_assert_cmpuint (result.contacts->len, ==, 1); g_assert_cmpuint (result.invalid->len, ==, 0); g_assert_no_error (result.error); g_assert (g_ptr_array_index (result.contacts, 0) != NULL); contact = g_object_ref (g_ptr_array_index (result.contacts, 0)); g_assert_cmpuint (tp_contact_get_handle (contact), ==, handle); g_assert_cmpstr (tp_contact_get_identifier (contact), ==, "alice"); /* fallback alias is still in effect */ g_assert_cmpstr (tp_contact_get_alias (contact), ==, "alice"); /* clean up before doing the second request */ reset_result (&result); g_assert (result.error == NULL); /* the second request enables the Alias feature, so it must make more D-Bus * round trips */ tp_connection_get_contacts_by_handle (f->client_conn, 1, &handle, 1, &feature, by_handle_cb, &result, finish, NULL); g_main_loop_run (result.loop); g_assert_cmpuint (result.contacts->len, ==, 1); g_assert_cmpuint (result.invalid->len, ==, 0); g_assert_no_error (result.error); g_assert (g_ptr_array_index (result.contacts, 0) == contact); g_assert_cmpstr (tp_contact_get_alias (contact), ==, "Alice in Wonderland"); g_assert (result.error == NULL); reset_result (&result); weak_pointer = contact; g_object_add_weak_pointer ((GObject *) contact, &weak_pointer); g_object_unref (contact); g_assert (weak_pointer == NULL); tp_tests_proxy_run_until_dbus_queue_processed (f->client_conn); g_main_loop_unref (result.loop); } static void test_no_features (Fixture *f, gconstpointer unused G_GNUC_UNUSED) { TpTestsContactsConnection *service_conn = f->service_conn; TpConnection *client_conn = f->client_conn; Result result = { g_main_loop_new (NULL, FALSE), NULL, NULL, NULL }; const gchar * const ids[] = { "alice", "bob", "chris" }; TpHandle handles[3] = { 0, 0, 0 }; TpHandleRepoIface *service_repo = tp_base_connection_get_handles ( (TpBaseConnection *) service_conn, TP_HANDLE_TYPE_CONTACT); TpContact *contacts[3]; guint i; g_message (G_STRFUNC); for (i = 0; i < 3; i++) handles[i] = tp_handle_ensure (service_repo, ids[i], NULL, NULL); tp_connection_get_contacts_by_handle (client_conn, 3, handles, 0, NULL, by_handle_cb, &result, finish, NULL); g_main_loop_run (result.loop); MYASSERT (result.contacts->len == 3, ": %u", result.contacts->len); MYASSERT (result.invalid->len == 0, ": %u", result.invalid->len); g_assert_no_error (result.error); MYASSERT (g_ptr_array_index (result.contacts, 0) != NULL, ""); MYASSERT (g_ptr_array_index (result.contacts, 1) != NULL, ""); MYASSERT (g_ptr_array_index (result.contacts, 2) != NULL, ""); for (i = 0; i < 3; i++) contacts[i] = g_object_ref (g_ptr_array_index (result.contacts, i)); g_assert (result.error == NULL); reset_result (&result); for (i = 0; i < 3; i++) { MYASSERT (tp_contact_get_connection (contacts[i]) == client_conn, ""); g_assert_cmpuint (tp_contact_get_handle (contacts[i]), ==, handles[i]); g_assert_cmpstr (tp_contact_get_identifier (contacts[i]), ==, ids[i]); g_assert_cmpstr (tp_contact_get_alias (contacts[i]), ==, tp_contact_get_identifier (contacts[i])); MYASSERT (tp_contact_get_avatar_token (contacts[i]) == NULL, ": %s", tp_contact_get_avatar_token (contacts[i])); g_assert_cmpuint (tp_contact_get_presence_type (contacts[i]), ==, TP_CONNECTION_PRESENCE_TYPE_UNSET); g_assert_cmpstr (tp_contact_get_presence_status (contacts[i]), ==, ""); g_assert_cmpstr (tp_contact_get_presence_message (contacts[i]), ==, ""); MYASSERT (!tp_contact_has_feature (contacts[i], TP_CONTACT_FEATURE_ALIAS), ""); MYASSERT (!tp_contact_has_feature (contacts[i], TP_CONTACT_FEATURE_AVATAR_TOKEN), ""); MYASSERT (!tp_contact_has_feature (contacts[i], TP_CONTACT_FEATURE_PRESENCE), ""); MYASSERT (!tp_contact_has_feature (contacts[i], TP_CONTACT_FEATURE_LOCATION), ""); MYASSERT (!tp_contact_has_feature (contacts[i], TP_CONTACT_FEATURE_CAPABILITIES), ""); } for (i = 0; i < 3; i++) { g_object_unref (contacts[i]); tp_tests_proxy_run_until_dbus_queue_processed (client_conn); tp_handle_unref (service_repo, handles[i]); } /* remaining cleanup */ g_main_loop_unref (result.loop); } static void upgrade_cb (TpConnection *connection, guint n_contacts, TpContact * const *contacts, const GError *error, gpointer user_data, GObject *weak_object) { Result *result = user_data; g_assert (result->invalid == NULL); g_assert (result->contacts == NULL); g_assert (result->error == NULL); g_assert (result->good_ids == NULL); g_assert (result->bad_ids == NULL); if (error == NULL) { guint i; DEBUG ("got %u contacts", n_contacts); result->contacts = g_ptr_array_sized_new (n_contacts); for (i = 0; i < n_contacts; i++) { TpContact *contact = contacts[i]; DEBUG ("contact #%u: %p", i, contact); DEBUG ("contact #%u alias: %s", i, tp_contact_get_alias (contact)); DEBUG ("contact #%u avatar token: %s", i, tp_contact_get_avatar_token (contact)); DEBUG ("contact #%u presence type: %u", i, tp_contact_get_presence_type (contact)); DEBUG ("contact #%u presence status: %s", i, tp_contact_get_presence_status (contact)); DEBUG ("contact #%u presence message: %s", i, tp_contact_get_presence_message (contact)); g_ptr_array_add (result->contacts, g_object_ref (contact)); } } else { DEBUG ("got an error: %s %u: %s", g_quark_to_string (error->domain), error->code, error->message); result->error = g_error_copy (error); } } /* Just put a country in locations for easier comparaisons. * FIXME: Ideally we should have a MYASSERT_SAME_ASV */ #define ASSERT_SAME_LOCATION(left, left_vardict, right)\ G_STMT_START {\ g_assert_cmpuint (g_hash_table_size (left), ==, \ g_hash_table_size (right));\ g_assert_cmpstr (tp_asv_get_string (left, "country"), ==,\ tp_asv_get_string (right, "country"));\ \ g_assert_cmpstr (g_variant_get_type_string (left_vardict), ==, "a{sv}"); \ g_assert_cmpuint (g_variant_n_children (left_vardict), ==, \ g_hash_table_size (right));\ g_assert_cmpstr (tp_vardict_get_string (left_vardict, "country"), ==,\ tp_asv_get_string (right, "country"));\ } G_STMT_END static void free_rcc_list (GPtrArray *rccs) { g_boxed_free (TP_ARRAY_TYPE_REQUESTABLE_CHANNEL_CLASS_LIST, rccs); } static void add_text_chat_class (GPtrArray *classes, TpHandleType handle_type) { GHashTable *fixed; const gchar * const allowed[] = { NULL }; GValueArray *arr; fixed = tp_asv_new ( TP_PROP_CHANNEL_CHANNEL_TYPE, G_TYPE_STRING, TP_IFACE_CHANNEL_TYPE_TEXT, TP_PROP_CHANNEL_TARGET_HANDLE_TYPE, G_TYPE_UINT, handle_type, NULL); arr = tp_value_array_build (2, TP_HASH_TYPE_STRING_VARIANT_MAP, fixed, G_TYPE_STRV, allowed, G_TYPE_INVALID); g_hash_table_unref (fixed); g_ptr_array_add (classes, arr); } static GHashTable * create_contact_caps (TpHandle *handles) { GHashTable *capabilities; GPtrArray *caps1, *caps2, *caps3; capabilities = g_hash_table_new_full (NULL, NULL, NULL, (GDestroyNotify) free_rcc_list); /* Support private text chats */ caps1 = g_ptr_array_sized_new (2); add_text_chat_class (caps1, TP_HANDLE_TYPE_CONTACT); g_hash_table_insert (capabilities, GUINT_TO_POINTER (handles[0]), caps1); /* Support text chatrooms */ caps2 = g_ptr_array_sized_new (1); add_text_chat_class (caps2, TP_HANDLE_TYPE_ROOM); g_hash_table_insert (capabilities, GUINT_TO_POINTER (handles[1]), caps2); /* Don't support anything */ caps3 = g_ptr_array_sized_new (0); g_hash_table_insert (capabilities, GUINT_TO_POINTER (handles[2]), caps3); return capabilities; } static void test_upgrade (Fixture *f, gconstpointer unused G_GNUC_UNUSED) { TpTestsContactsConnection *service_conn = f->service_conn; TpConnection *client_conn = f->client_conn; Result result = { g_main_loop_new (NULL, FALSE), NULL, NULL, NULL }; TpHandle handles[] = { 0, 0, 0 }; static const gchar * const ids[] = { "alice", "bob", "chris" }; static const gchar * const aliases[] = { "Alice in Wonderland", "Bob the Builder", "Christopher Robin" }; static const gchar * const tokens[] = { "aaaaa", "bbbbb", "ccccc" }; static TpTestsContactsConnectionPresenceStatusIndex statuses[] = { TP_TESTS_CONTACTS_CONNECTION_STATUS_AVAILABLE, TP_TESTS_CONTACTS_CONNECTION_STATUS_BUSY, TP_TESTS_CONTACTS_CONNECTION_STATUS_AWAY }; static const gchar * const messages[] = { "", "Fixing it", "GON OUT BACKSON" }; GHashTable *location_1 = tp_asv_new ( "country", G_TYPE_STRING, "United Kingdom of Great Britain and Northern Ireland", NULL); GHashTable *location_2 = tp_asv_new ( "country", G_TYPE_STRING, "Atlantis", NULL); GHashTable *location_3 = tp_asv_new ( "country", G_TYPE_STRING, "Belgium", NULL); GHashTable *locations[] = { location_1, location_2, location_3 }; GHashTable *capabilities; TpHandleRepoIface *service_repo = tp_base_connection_get_handles ( (TpBaseConnection *) service_conn, TP_HANDLE_TYPE_CONTACT); TpContact *contacts[3]; TpContactFeature features[] = { TP_CONTACT_FEATURE_ALIAS, TP_CONTACT_FEATURE_AVATAR_TOKEN, TP_CONTACT_FEATURE_PRESENCE, TP_CONTACT_FEATURE_LOCATION, TP_CONTACT_FEATURE_CAPABILITIES }; guint i; g_message (G_STRFUNC); for (i = 0; i < 3; i++) handles[i] = tp_handle_ensure (service_repo, ids[i], NULL, NULL); tp_tests_contacts_connection_change_aliases (service_conn, 3, handles, aliases); tp_tests_contacts_connection_change_presences (service_conn, 3, handles, statuses, messages); tp_tests_contacts_connection_change_avatar_tokens (service_conn, 3, handles, tokens); tp_tests_contacts_connection_change_locations (service_conn, 3, handles, locations); capabilities = create_contact_caps (handles); tp_tests_contacts_connection_change_capabilities (service_conn, capabilities); g_hash_table_unref (capabilities); tp_connection_get_contacts_by_handle (client_conn, 3, handles, 0, NULL, by_handle_cb, &result, finish, NULL); g_main_loop_run (result.loop); MYASSERT (result.contacts->len == 3, ": %u", result.contacts->len); MYASSERT (result.invalid->len == 0, ": %u", result.invalid->len); g_assert_no_error (result.error); MYASSERT (g_ptr_array_index (result.contacts, 0) != NULL, ""); MYASSERT (g_ptr_array_index (result.contacts, 1) != NULL, ""); MYASSERT (g_ptr_array_index (result.contacts, 2) != NULL, ""); for (i = 0; i < 3; i++) contacts[i] = g_object_ref (g_ptr_array_index (result.contacts, i)); for (i = 0; i < 3; i++) { MYASSERT (tp_contact_get_connection (contacts[i]) == client_conn, ""); g_assert_cmpuint (tp_contact_get_handle (contacts[i]), ==, handles[i]); g_assert_cmpstr (tp_contact_get_identifier (contacts[i]), ==, ids[i]); g_assert_cmpstr (tp_contact_get_alias (contacts[i]), ==, tp_contact_get_identifier (contacts[i])); MYASSERT (tp_contact_get_avatar_token (contacts[i]) == NULL, ": %s", tp_contact_get_avatar_token (contacts[i])); g_assert_cmpuint (tp_contact_get_presence_type (contacts[i]), ==, TP_CONNECTION_PRESENCE_TYPE_UNSET); g_assert_cmpstr (tp_contact_get_presence_status (contacts[i]), ==, ""); g_assert_cmpstr (tp_contact_get_presence_message (contacts[i]), ==, ""); MYASSERT (!tp_contact_has_feature (contacts[i], TP_CONTACT_FEATURE_ALIAS), ""); MYASSERT (!tp_contact_has_feature (contacts[i], TP_CONTACT_FEATURE_AVATAR_TOKEN), ""); MYASSERT (!tp_contact_has_feature (contacts[i], TP_CONTACT_FEATURE_PRESENCE), ""); MYASSERT (!tp_contact_has_feature (contacts[i], TP_CONTACT_FEATURE_LOCATION), ""); MYASSERT (!tp_contact_has_feature (contacts[i], TP_CONTACT_FEATURE_CAPABILITIES), ""); } /* clean up before doing the second request */ g_assert (result.error == NULL); reset_result (&result); tp_connection_upgrade_contacts (client_conn, 3, contacts, G_N_ELEMENTS (features), features, upgrade_cb, &result, finish, NULL); g_main_loop_run (result.loop); MYASSERT (result.contacts->len == 3, ": %u", result.contacts->len); MYASSERT (result.invalid == NULL, ""); g_assert_no_error (result.error); for (i = 0; i < 3; i++) { MYASSERT (g_ptr_array_index (result.contacts, 0) == contacts[0], ""); } g_assert (result.invalid == NULL); g_assert (result.error == NULL); reset_result (&result); for (i = 0; i < 3; i++) { GVariant *vardict; g_assert_cmpuint (tp_contact_get_handle (contacts[i]), ==, handles[i]); g_assert_cmpstr (tp_contact_get_identifier (contacts[i]), ==, ids[i]); MYASSERT (tp_contact_has_feature (contacts[i], TP_CONTACT_FEATURE_ALIAS), ""); g_assert_cmpstr (tp_contact_get_alias (contacts[i]), ==, aliases[i]); MYASSERT (tp_contact_has_feature (contacts[i], TP_CONTACT_FEATURE_AVATAR_TOKEN), ""); g_assert_cmpstr (tp_contact_get_avatar_token (contacts[i]), ==, tokens[i]); MYASSERT (tp_contact_has_feature (contacts[i], TP_CONTACT_FEATURE_PRESENCE), ""); g_assert_cmpstr (tp_contact_get_presence_message (contacts[i]), ==, messages[i]); MYASSERT (tp_contact_has_feature (contacts[i], TP_CONTACT_FEATURE_LOCATION), ""); vardict = tp_contact_dup_location (contacts[i]); ASSERT_SAME_LOCATION (tp_contact_get_location (contacts[i]), vardict, locations[i]); g_variant_unref (vardict); g_object_get (contacts[i], "location-vardict", &vardict, NULL); ASSERT_SAME_LOCATION (tp_contact_get_location (contacts[i]), vardict, locations[i]); g_variant_unref (vardict); MYASSERT (tp_contact_has_feature (contacts[i], TP_CONTACT_FEATURE_CAPABILITIES), ""); MYASSERT (tp_contact_get_capabilities (contacts[i]) != NULL, ""); } g_assert_cmpuint (tp_contact_get_presence_type (contacts[0]), ==, TP_CONNECTION_PRESENCE_TYPE_AVAILABLE); g_assert_cmpstr (tp_contact_get_presence_status (contacts[0]), ==, "available"); g_assert_cmpuint (tp_contact_get_presence_type (contacts[1]), ==, TP_CONNECTION_PRESENCE_TYPE_BUSY); g_assert_cmpstr (tp_contact_get_presence_status (contacts[1]), ==, "busy"); g_assert_cmpuint (tp_contact_get_presence_type (contacts[2]), ==, TP_CONNECTION_PRESENCE_TYPE_AWAY); g_assert_cmpstr (tp_contact_get_presence_status (contacts[2]), ==, "away"); for (i = 0; i < 3; i++) { g_object_unref (contacts[i]); tp_tests_proxy_run_until_dbus_queue_processed (client_conn); tp_handle_unref (service_repo, handles[i]); } /* remaining cleanup */ g_hash_table_unref (location_1); g_hash_table_unref (location_2); g_hash_table_unref (location_3); g_main_loop_unref (result.loop); } /* Regression test case for fd.o#41414 */ static void test_upgrade_noop (Fixture *f, gconstpointer unused G_GNUC_UNUSED) { Result result = { g_main_loop_new (NULL, FALSE), NULL, NULL, NULL }; TpHandle handle; TpContact *contact; g_test_bug ("41414"); /* Get a contact by handle */ handle = get_handle_with_no_caps (f, "test-upgrade-noop"); tp_connection_get_contacts_by_handle (f->client_conn, 1, &handle, G_N_ELEMENTS (all_contact_features), all_contact_features, by_handle_cb, &result, finish, NULL); g_main_loop_run (result.loop); g_assert_no_error (result.error); contact = g_object_ref (g_ptr_array_index (result.contacts, 0)); reset_result (&result); /* Upgrade it, but it should already have all features */ make_the_connection_disappear (f); tp_connection_upgrade_contacts (f->client_conn, 1, &contact, G_N_ELEMENTS (all_contact_features), all_contact_features, upgrade_cb, &result, finish, NULL); g_main_loop_run (result.loop); g_assert_no_error (result.error); reset_result (&result); put_the_connection_back (f); g_object_unref (contact); } typedef struct { gboolean alias_changed; gboolean avatar_token_changed; gboolean presence_type_changed; gboolean presence_status_changed; gboolean presence_msg_changed; gboolean location_changed; gboolean location_vardict_changed; gboolean capabilities_changed; } notify_ctx; static void notify_ctx_init (notify_ctx *ctx) { ctx->alias_changed = FALSE; ctx->avatar_token_changed = FALSE; ctx->presence_type_changed = FALSE; ctx->presence_status_changed = FALSE; ctx->presence_msg_changed = FALSE; ctx->location_changed = FALSE; ctx->location_vardict_changed = FALSE; ctx->capabilities_changed = FALSE; } static gboolean notify_ctx_is_fully_changed (notify_ctx *ctx) { return ctx->alias_changed && ctx->avatar_token_changed && ctx->presence_type_changed && ctx->presence_status_changed && ctx->presence_msg_changed && ctx->location_changed && ctx->location_vardict_changed && ctx->capabilities_changed; } static gboolean notify_ctx_is_changed (notify_ctx *ctx) { return ctx->alias_changed || ctx->avatar_token_changed || ctx->presence_type_changed || ctx->presence_status_changed || ctx->presence_msg_changed || ctx->location_changed || ctx->location_vardict_changed || ctx->capabilities_changed; } static void contact_notify_cb (TpContact *contact, GParamSpec *param, notify_ctx *ctx) { if (!tp_strdiff (param->name, "alias")) ctx->alias_changed = TRUE; else if (!tp_strdiff (param->name, "avatar-token")) ctx->avatar_token_changed = TRUE; else if (!tp_strdiff (param->name, "presence-type")) ctx->presence_type_changed = TRUE; else if (!tp_strdiff (param->name, "presence-status")) ctx->presence_status_changed = TRUE; else if (!tp_strdiff (param->name, "presence-message")) ctx->presence_msg_changed = TRUE; else if (!tp_strdiff (param->name, "location")) ctx->location_changed = TRUE; else if (!tp_strdiff (param->name, "location-vardict")) ctx->location_vardict_changed = TRUE; else if (!tp_strdiff (param->name, "capabilities")) ctx->capabilities_changed = TRUE; } static GHashTable * create_new_contact_caps (TpHandle *handles) { GHashTable *capabilities; GPtrArray *caps1, *caps2; capabilities = g_hash_table_new_full (NULL, NULL, NULL, (GDestroyNotify) free_rcc_list); /* Support private text chats and chatrooms */ caps1 = g_ptr_array_sized_new (2); add_text_chat_class (caps1, TP_HANDLE_TYPE_CONTACT); add_text_chat_class (caps1, TP_HANDLE_TYPE_ROOM); g_hash_table_insert (capabilities, GUINT_TO_POINTER (handles[0]), caps1); /* Don't support anything */ caps2 = g_ptr_array_sized_new (0); g_hash_table_insert (capabilities, GUINT_TO_POINTER (handles[1]), caps2); return capabilities; } static void test_features (Fixture *f, gconstpointer unused G_GNUC_UNUSED) { TpTestsContactsConnection *service_conn = f->service_conn; TpConnection *client_conn = f->client_conn; Result result = { g_main_loop_new (NULL, FALSE), NULL, NULL, NULL }; TpHandle handles[] = { 0, 0, 0 }; static const gchar * const ids[] = { "alice", "bob", "chris" }; static const gchar * const aliases[] = { "Alice in Wonderland", "Bob the Builder", "Christopher Robin" }; static const gchar * const tokens[] = { "aaaaa", "bbbbb", "ccccc" }; static TpTestsContactsConnectionPresenceStatusIndex statuses[] = { TP_TESTS_CONTACTS_CONNECTION_STATUS_AVAILABLE, TP_TESTS_CONTACTS_CONNECTION_STATUS_BUSY, TP_TESTS_CONTACTS_CONNECTION_STATUS_AWAY }; static const gchar * const messages[] = { "", "Fixing it", "GON OUT BACKSON" }; static const gchar * const new_aliases[] = { "Alice [at a tea party]", "Bob the Plumber" }; static const gchar * const new_tokens[] = { "AAAA", "BBBB" }; static TpTestsContactsConnectionPresenceStatusIndex new_statuses[] = { TP_TESTS_CONTACTS_CONNECTION_STATUS_AWAY, TP_TESTS_CONTACTS_CONNECTION_STATUS_AVAILABLE }; static const gchar * const new_messages[] = { "At the Mad Hatter's", "It'll cost you" }; GHashTable *location_1 = tp_asv_new ( "country", G_TYPE_STRING, "United Kingdom of Great Britain and Northern Ireland", NULL); GHashTable *location_2 = tp_asv_new ( "country", G_TYPE_STRING, "Atlantis", NULL); GHashTable *location_3 = tp_asv_new ( "country", G_TYPE_STRING, "Belgium", NULL); GHashTable *locations[] = { location_1, location_2, location_3 }; GHashTable *location_4 = tp_asv_new ( "country", G_TYPE_STRING, "France", NULL); GHashTable *location_5 = tp_asv_new ( "country", G_TYPE_STRING, "Éire", NULL); GHashTable *new_locations[] = { location_4, location_5 }; GHashTable *capabilities, *new_capabilities; gboolean support_text_chats[] = { TRUE, FALSE, FALSE }; gboolean support_text_chatrooms[] = { FALSE, TRUE, FALSE }; gboolean new_support_text_chats[] = { TRUE, FALSE }; gboolean new_support_text_chatrooms[] = { TRUE, FALSE }; TpHandleRepoIface *service_repo = tp_base_connection_get_handles ( (TpBaseConnection *) service_conn, TP_HANDLE_TYPE_CONTACT); TpContact *contacts[3]; TpContactFeature features[] = { TP_CONTACT_FEATURE_ALIAS, TP_CONTACT_FEATURE_AVATAR_TOKEN, TP_CONTACT_FEATURE_PRESENCE, TP_CONTACT_FEATURE_LOCATION, TP_CONTACT_FEATURE_CAPABILITIES }; guint i; struct { TpConnection *connection; TpHandle handle; gchar *identifier; gchar *alias; gchar *avatar_token; TpConnectionPresenceType presence_type; gchar *presence_status; gchar *presence_message; GHashTable *location; GVariant *location_vardict; TpCapabilities *capabilities; } from_gobject; notify_ctx notify_ctx_alice, notify_ctx_chris; GVariant *vardict; g_message (G_STRFUNC); for (i = 0; i < 3; i++) handles[i] = tp_handle_ensure (service_repo, ids[i], NULL, NULL); tp_tests_contacts_connection_change_aliases (service_conn, 3, handles, aliases); tp_tests_contacts_connection_change_presences (service_conn, 3, handles, statuses, messages); tp_tests_contacts_connection_change_avatar_tokens (service_conn, 3, handles, tokens); tp_tests_contacts_connection_change_locations (service_conn, 3, handles, locations); /* contact capabilities */ capabilities = create_contact_caps (handles); tp_tests_contacts_connection_change_capabilities (service_conn, capabilities); g_hash_table_unref (capabilities); tp_connection_get_contacts_by_handle (client_conn, 3, handles, G_N_ELEMENTS (features), features, by_handle_cb, &result, finish, NULL); g_main_loop_run (result.loop); MYASSERT (result.contacts->len == 3, ": %u", result.contacts->len); MYASSERT (result.invalid->len == 0, ": %u", result.invalid->len); g_assert_no_error (result.error); MYASSERT (g_ptr_array_index (result.contacts, 0) != NULL, ""); MYASSERT (g_ptr_array_index (result.contacts, 1) != NULL, ""); MYASSERT (g_ptr_array_index (result.contacts, 2) != NULL, ""); for (i = 0; i < 3; i++) contacts[i] = g_object_ref (g_ptr_array_index (result.contacts, i)); g_assert (result.error == NULL); reset_result (&result); for (i = 0; i < 3; i++) { TpCapabilities *caps; g_assert_cmpuint (tp_contact_get_handle (contacts[i]), ==, handles[i]); g_assert_cmpstr (tp_contact_get_identifier (contacts[i]), ==, ids[i]); MYASSERT (tp_contact_has_feature (contacts[i], TP_CONTACT_FEATURE_ALIAS), ""); g_assert_cmpstr (tp_contact_get_alias (contacts[i]), ==, aliases[i]); MYASSERT (tp_contact_has_feature (contacts[i], TP_CONTACT_FEATURE_AVATAR_TOKEN), ""); g_assert_cmpstr (tp_contact_get_avatar_token (contacts[i]), ==, tokens[i]); MYASSERT (tp_contact_has_feature (contacts[i], TP_CONTACT_FEATURE_PRESENCE), ""); g_assert_cmpstr (tp_contact_get_presence_message (contacts[i]), ==, messages[i]); MYASSERT (tp_contact_has_feature (contacts[i], TP_CONTACT_FEATURE_LOCATION), ""); vardict = tp_contact_dup_location (contacts[i]); ASSERT_SAME_LOCATION (tp_contact_get_location (contacts[i]), vardict, locations[i]); g_variant_unref (vardict); MYASSERT (tp_contact_has_feature (contacts[i], TP_CONTACT_FEATURE_CAPABILITIES), ""); caps = tp_contact_get_capabilities (contacts[i]); MYASSERT (caps != NULL, ""); MYASSERT (tp_capabilities_is_specific_to_contact (caps), ""); MYASSERT (tp_capabilities_supports_text_chats (caps) == support_text_chats[i], " contact %u", i); MYASSERT (tp_capabilities_supports_text_chatrooms (caps) == support_text_chatrooms[i], " contact %u", i); } g_assert_cmpuint (tp_contact_get_presence_type (contacts[0]), ==, TP_CONNECTION_PRESENCE_TYPE_AVAILABLE); g_assert_cmpstr (tp_contact_get_presence_status (contacts[0]), ==, "available"); g_assert_cmpuint (tp_contact_get_presence_type (contacts[1]), ==, TP_CONNECTION_PRESENCE_TYPE_BUSY); g_assert_cmpstr (tp_contact_get_presence_status (contacts[1]), ==, "busy"); g_assert_cmpuint (tp_contact_get_presence_type (contacts[2]), ==, TP_CONNECTION_PRESENCE_TYPE_AWAY); g_assert_cmpstr (tp_contact_get_presence_status (contacts[2]), ==, "away"); /* exercise GObject properties in a basic way */ g_object_get (contacts[0], "connection", &from_gobject.connection, "handle", &from_gobject.handle, "identifier", &from_gobject.identifier, "alias", &from_gobject.alias, "avatar-token", &from_gobject.avatar_token, "presence-type", &from_gobject.presence_type, "presence-status", &from_gobject.presence_status, "presence-message", &from_gobject.presence_message, "location", &from_gobject.location, "location-vardict", &from_gobject.location_vardict, "capabilities", &from_gobject.capabilities, NULL); MYASSERT (from_gobject.connection == client_conn, ""); g_assert_cmpuint (from_gobject.handle, ==, handles[0]); g_assert_cmpstr (from_gobject.identifier, ==, "alice"); g_assert_cmpstr (from_gobject.alias, ==, "Alice in Wonderland"); g_assert_cmpstr (from_gobject.avatar_token, ==, "aaaaa"); g_assert_cmpuint (from_gobject.presence_type, ==, TP_CONNECTION_PRESENCE_TYPE_AVAILABLE); g_assert_cmpstr (from_gobject.presence_status, ==, "available"); g_assert_cmpstr (from_gobject.presence_message, ==, ""); ASSERT_SAME_LOCATION (from_gobject.location, from_gobject.location_vardict, locations[0]); MYASSERT (tp_capabilities_is_specific_to_contact (from_gobject.capabilities), ""); MYASSERT (tp_capabilities_supports_text_chats (from_gobject.capabilities) == support_text_chats[0], ""); MYASSERT (tp_capabilities_supports_text_chatrooms (from_gobject.capabilities) == support_text_chatrooms[0], ""); g_object_unref (from_gobject.connection); g_free (from_gobject.identifier); g_free (from_gobject.alias); g_free (from_gobject.avatar_token); g_free (from_gobject.presence_status); g_free (from_gobject.presence_message); g_hash_table_unref (from_gobject.location); g_variant_unref (from_gobject.location_vardict); g_object_unref (from_gobject.capabilities); notify_ctx_init (¬ify_ctx_alice); g_signal_connect (contacts[0], "notify", G_CALLBACK (contact_notify_cb), ¬ify_ctx_alice); notify_ctx_init (¬ify_ctx_chris); g_signal_connect (contacts[2], "notify", G_CALLBACK (contact_notify_cb), ¬ify_ctx_chris); /* Change Alice and Bob's contact info, leave Chris as-is */ tp_tests_contacts_connection_change_aliases (service_conn, 2, handles, new_aliases); tp_tests_contacts_connection_change_presences (service_conn, 2, handles, new_statuses, new_messages); tp_tests_contacts_connection_change_avatar_tokens (service_conn, 2, handles, new_tokens); tp_tests_contacts_connection_change_locations (service_conn, 2, handles, new_locations); new_capabilities = create_new_contact_caps (handles); tp_tests_contacts_connection_change_capabilities (service_conn, new_capabilities); g_hash_table_unref (new_capabilities); tp_tests_proxy_run_until_dbus_queue_processed (client_conn); g_assert (notify_ctx_is_fully_changed (¬ify_ctx_alice)); g_assert (!notify_ctx_is_changed (¬ify_ctx_chris)); for (i = 0; i < 2; i++) { TpCapabilities *caps; g_assert_cmpuint (tp_contact_get_handle (contacts[i]), ==, handles[i]); g_assert_cmpstr (tp_contact_get_identifier (contacts[i]), ==, ids[i]); MYASSERT (tp_contact_has_feature (contacts[i], TP_CONTACT_FEATURE_ALIAS), ""); g_assert_cmpstr (tp_contact_get_alias (contacts[i]), ==, new_aliases[i]); MYASSERT (tp_contact_has_feature (contacts[i], TP_CONTACT_FEATURE_AVATAR_TOKEN), ""); g_assert_cmpstr (tp_contact_get_avatar_token (contacts[i]), ==, new_tokens[i]); MYASSERT (tp_contact_has_feature (contacts[i], TP_CONTACT_FEATURE_PRESENCE), ""); g_assert_cmpstr (tp_contact_get_presence_message (contacts[i]), ==, new_messages[i]); MYASSERT (tp_contact_has_feature (contacts[i], TP_CONTACT_FEATURE_LOCATION), ""); vardict = tp_contact_dup_location (contacts[i]); ASSERT_SAME_LOCATION (tp_contact_get_location (contacts[i]), vardict, new_locations[i]); g_variant_unref (vardict); caps = tp_contact_get_capabilities (contacts[i]); MYASSERT (caps != NULL, ""); MYASSERT (tp_capabilities_is_specific_to_contact (caps), ""); MYASSERT (tp_capabilities_supports_text_chats (caps) == new_support_text_chats[i], " contact %u", i); MYASSERT (tp_capabilities_supports_text_chatrooms (caps) == new_support_text_chatrooms[i], " contact %u", i); } g_assert_cmpuint (tp_contact_get_presence_type (contacts[0]), ==, TP_CONNECTION_PRESENCE_TYPE_AWAY); g_assert_cmpstr (tp_contact_get_presence_status (contacts[0]), ==, "away"); g_assert_cmpuint (tp_contact_get_presence_type (contacts[1]), ==, TP_CONNECTION_PRESENCE_TYPE_AVAILABLE); g_assert_cmpstr (tp_contact_get_presence_status (contacts[1]), ==, "available"); for (i = 0; i < 3; i++) { g_object_unref (contacts[i]); tp_tests_proxy_run_until_dbus_queue_processed (client_conn); tp_handle_unref (service_repo, handles[i]); } /* remaining cleanup */ g_main_loop_unref (result.loop); g_hash_table_unref (location_1); g_hash_table_unref (location_2); g_hash_table_unref (location_3); g_hash_table_unref (location_4); g_hash_table_unref (location_5); } static void by_id_cb (TpConnection *connection, guint n_contacts, TpContact * const *contacts, const gchar * const *good_ids, GHashTable *bad_ids, const GError *error, gpointer user_data, GObject *weak_object) { Result *result = user_data; g_assert (result->invalid == NULL); g_assert (result->contacts == NULL); g_assert (result->error == NULL); g_assert (result->good_ids == NULL); g_assert (result->bad_ids == NULL); if (error == NULL) { GHashTableIter iter; gpointer key, value; guint i; DEBUG ("got %u contacts and %u bad IDs", n_contacts, g_hash_table_size (bad_ids)); result->bad_ids = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, (GDestroyNotify) g_error_free); tp_g_hash_table_update (result->bad_ids, bad_ids, (GBoxedCopyFunc) g_strdup, (GBoxedCopyFunc) g_error_copy); g_hash_table_iter_init (&iter, result->bad_ids); while (g_hash_table_iter_next (&iter, &key, &value)) { gchar *id = key; GError *e = value; DEBUG ("bad ID %s: %s %u: %s", id, g_quark_to_string (e->domain), e->code, e->message); } result->good_ids = g_strdupv ((GStrv) good_ids); result->contacts = g_ptr_array_sized_new (n_contacts); for (i = 0; i < n_contacts; i++) { TpContact *contact = contacts[i]; DEBUG ("contact #%u: %p", i, contact); DEBUG ("contact #%u we asked for ID %s", i, good_ids[i]); DEBUG ("contact #%u we got ID %s", i, tp_contact_get_identifier (contact)); DEBUG ("contact #%u alias: %s", i, tp_contact_get_alias (contact)); DEBUG ("contact #%u avatar token: %s", i, tp_contact_get_avatar_token (contact)); DEBUG ("contact #%u presence type: %u", i, tp_contact_get_presence_type (contact)); DEBUG ("contact #%u presence status: %s", i, tp_contact_get_presence_status (contact)); DEBUG ("contact #%u presence message: %s", i, tp_contact_get_presence_message (contact)); g_ptr_array_add (result->contacts, g_object_ref (contact)); } } else { DEBUG ("got an error: %s %u: %s", g_quark_to_string (error->domain), error->code, error->message); result->error = g_error_copy (error); } } static void test_by_id (Fixture *f, gconstpointer unused G_GNUC_UNUSED) { TpConnection *client_conn = f->client_conn; Result result = { g_main_loop_new (NULL, FALSE) }; static const gchar * const ids[] = { "Alice", "Bob", "Not valid", "Chris", "not valid either", NULL }; TpContact *contacts[3]; GError *e /* no initialization needed */; g_message ("%s: all bad (fd.o #19688)", G_STRFUNC); tp_connection_get_contacts_by_id (client_conn, 1, ids + 2, 0, NULL, by_id_cb, &result, finish, NULL); g_main_loop_run (result.loop); MYASSERT (result.contacts->len == 0, ": %u", result.contacts->len); MYASSERT (g_hash_table_size (result.bad_ids) == 1, ": %u", g_hash_table_size (result.bad_ids)); g_assert_no_error (result.error); e = g_hash_table_lookup (result.bad_ids, "Not valid"); MYASSERT (e != NULL, ""); reset_result (&result); g_message ("%s: all good", G_STRFUNC); tp_connection_get_contacts_by_id (client_conn, 2, ids, 0, NULL, by_id_cb, &result, finish, NULL); g_main_loop_run (result.loop); MYASSERT (result.contacts->len == 2, ": %u", result.contacts->len); MYASSERT (g_hash_table_size (result.bad_ids) == 0, ": %u", g_hash_table_size (result.bad_ids)); g_assert_no_error (result.error); MYASSERT (g_ptr_array_index (result.contacts, 0) != NULL, ""); MYASSERT (g_ptr_array_index (result.contacts, 1) != NULL, ""); contacts[0] = g_ptr_array_index (result.contacts, 0); g_assert_cmpstr (result.good_ids[0], ==, "Alice"); g_assert_cmpstr (tp_contact_get_identifier (contacts[0]), ==, "alice"); contacts[1] = g_ptr_array_index (result.contacts, 1); g_assert_cmpstr (result.good_ids[1], ==, "Bob"); g_assert_cmpstr (tp_contact_get_identifier (contacts[1]), ==, "bob"); reset_result (&result); g_message ("%s: not all good", G_STRFUNC); tp_connection_get_contacts_by_id (client_conn, 5, ids, 0, NULL, by_id_cb, &result, finish, NULL); g_main_loop_run (result.loop); MYASSERT (result.contacts->len == 3, ": %u", result.contacts->len); MYASSERT (g_hash_table_size (result.bad_ids) == 2, ": %u", g_hash_table_size (result.bad_ids)); g_assert_no_error (result.error); e = g_hash_table_lookup (result.bad_ids, "Not valid"); MYASSERT (e != NULL, ""); e = g_hash_table_lookup (result.bad_ids, "not valid either"); MYASSERT (e != NULL, ""); MYASSERT (g_ptr_array_index (result.contacts, 0) != NULL, ""); MYASSERT (g_ptr_array_index (result.contacts, 1) != NULL, ""); MYASSERT (g_ptr_array_index (result.contacts, 2) != NULL, ""); contacts[0] = g_ptr_array_index (result.contacts, 0); g_assert_cmpstr (result.good_ids[0], ==, "Alice"); g_assert_cmpstr (tp_contact_get_identifier (contacts[0]), ==, "alice"); contacts[1] = g_ptr_array_index (result.contacts, 1); g_assert_cmpstr (result.good_ids[1], ==, "Bob"); g_assert_cmpstr (tp_contact_get_identifier (contacts[1]), ==, "bob"); contacts[2] = g_ptr_array_index (result.contacts, 2); g_assert_cmpstr (result.good_ids[2], ==, "Chris"); g_assert_cmpstr (tp_contact_get_identifier (contacts[2]), ==, "chris"); /* wait for ReleaseHandles to run */ tp_tests_proxy_run_until_dbus_queue_processed (client_conn); /* remaining cleanup */ reset_result (&result); g_main_loop_unref (result.loop); } static void test_capabilities_without_contact_caps (Fixture *f, gconstpointer unused G_GNUC_UNUSED) { TpConnection *client_conn = f->legacy_client_conn; Result result = { g_main_loop_new (NULL, FALSE), NULL, NULL, NULL }; TpHandle handles[] = { 0, 0, 0 }; static const gchar * const ids[] = { "alice", "bob", "chris" }; TpHandleRepoIface *service_repo = tp_base_connection_get_handles ( f->legacy_base_connection, TP_HANDLE_TYPE_CONTACT); TpContact *contacts[3]; guint i; TpContactFeature features[] = { TP_CONTACT_FEATURE_CAPABILITIES }; g_message (G_STRFUNC); for (i = 0; i < 3; i++) handles[i] = tp_handle_ensure (service_repo, ids[i], NULL, NULL); tp_connection_get_contacts_by_handle (client_conn, 3, handles, G_N_ELEMENTS (features), features, by_handle_cb, &result, finish, NULL); g_main_loop_run (result.loop); MYASSERT (result.contacts->len == 3, ": %u", result.contacts->len); MYASSERT (result.invalid->len == 0, ": %u", result.invalid->len); g_assert_no_error (result.error); MYASSERT (g_ptr_array_index (result.contacts, 0) != NULL, ""); MYASSERT (g_ptr_array_index (result.contacts, 1) != NULL, ""); MYASSERT (g_ptr_array_index (result.contacts, 2) != NULL, ""); for (i = 0; i < 3; i++) contacts[i] = g_ptr_array_index (result.contacts, i); for (i = 0; i < 3; i++) { TpCapabilities *caps; g_assert_cmpuint (tp_contact_get_handle (contacts[i]), ==, handles[i]); g_assert_cmpstr (tp_contact_get_identifier (contacts[i]), ==, ids[i]); MYASSERT (tp_contact_has_feature (contacts[i], TP_CONTACT_FEATURE_CAPABILITIES), ""); caps = tp_contact_get_capabilities (contacts[i]); MYASSERT (caps != NULL, ""); MYASSERT (!tp_capabilities_is_specific_to_contact (caps), ""); MYASSERT (!tp_capabilities_supports_text_chats (caps), " contact %u", i); MYASSERT (!tp_capabilities_supports_text_chatrooms (caps), " contact %u", i); } g_assert (result.error == NULL); reset_result (&result); g_main_loop_unref (result.loop); } static void test_prepare_contact_caps_without_request (Fixture *f, gconstpointer unused G_GNUC_UNUSED) { TpConnection *client_conn = f->no_requests_client_conn; Result result = { g_main_loop_new (NULL, FALSE), NULL, NULL, NULL }; TpHandle handles[] = { 0, 0, 0 }; static const gchar * const ids[] = { "alice", "bob", "chris" }; TpHandleRepoIface *service_repo = tp_base_connection_get_handles ( f->no_requests_base_connection, TP_HANDLE_TYPE_CONTACT); TpContact *contacts[3]; guint i; TpContactFeature features[] = { TP_CONTACT_FEATURE_CAPABILITIES }; g_test_bug ("27686"); for (i = 0; i < 3; i++) handles[i] = tp_handle_ensure (service_repo, ids[i], NULL, NULL); tp_connection_get_contacts_by_handle (client_conn, 3, handles, G_N_ELEMENTS (features), features, by_handle_cb, &result, finish, NULL); g_main_loop_run (result.loop); MYASSERT (result.contacts->len == 3, ": %u", result.contacts->len); MYASSERT (result.invalid->len == 0, ": %u", result.invalid->len); g_assert_no_error (result.error); MYASSERT (g_ptr_array_index (result.contacts, 0) != NULL, ""); MYASSERT (g_ptr_array_index (result.contacts, 1) != NULL, ""); MYASSERT (g_ptr_array_index (result.contacts, 2) != NULL, ""); for (i = 0; i < 3; i++) contacts[i] = g_ptr_array_index (result.contacts, i); for (i = 0; i < 3; i++) { TpCapabilities *caps; g_assert_cmpuint (tp_contact_get_handle (contacts[i]), ==, handles[i]); g_assert_cmpstr (tp_contact_get_identifier (contacts[i]), ==, ids[i]); MYASSERT (!tp_contact_has_feature (contacts[i], TP_CONTACT_FEATURE_CAPABILITIES), ""); caps = tp_contact_get_capabilities (contacts[i]); MYASSERT (caps == NULL, ""); } g_assert (result.error == NULL); reset_result (&result); g_main_loop_unref (result.loop); } static void test_dup_if_possible (Fixture *f, gconstpointer unused G_GNUC_UNUSED) { TpHandle alice_handle, bob_handle; TpContact *alice; TpContact *contact; alice_handle = tp_handle_ensure (f->service_repo, "alice", NULL, NULL); g_assert_cmpuint (alice_handle, !=, 0); bob_handle = tp_handle_ensure (f->service_repo, "bob", NULL, NULL); g_assert_cmpuint (bob_handle, !=, 0); tp_connection_get_contacts_by_handle (f->client_conn, 1, &alice_handle, 0, NULL, by_handle_cb, &f->result, finish, NULL); g_main_loop_run (f->result.loop); g_assert_cmpuint (f->result.contacts->len, ==, 1); g_assert_cmpuint (f->result.invalid->len, ==, 0); g_assert_no_error (f->result.error); g_assert (g_ptr_array_index (f->result.contacts, 0) != NULL); alice = g_object_ref (g_ptr_array_index (f->result.contacts, 0)); g_assert_cmpuint (tp_contact_get_handle (alice), ==, alice_handle); g_assert_cmpstr (tp_contact_get_identifier (alice), ==, "alice"); reset_result (&f->result); /* we already have a cached TpContact for Alice, so we can get another * copy of it synchronously */ contact = tp_connection_dup_contact_if_possible (f->client_conn, alice_handle, "alice"); g_assert (contact == alice); g_object_unref (contact); contact = tp_connection_dup_contact_if_possible (f->client_conn, alice_handle, NULL); g_assert (contact == alice); g_object_unref (contact); /* because this connection has immortal handles, we can reliably get a * contact for Bob synchronously, but only if we supply his identifier */ contact = tp_connection_dup_contact_if_possible (f->client_conn, bob_handle, NULL); g_assert (contact == NULL); contact = tp_connection_dup_contact_if_possible (f->client_conn, bob_handle, "bob"); g_assert (contact != alice); g_assert_cmpstr (tp_contact_get_identifier (contact), ==, "bob"); g_assert_cmpuint (tp_contact_get_handle (contact), ==, bob_handle); g_object_unref (contact); g_object_unref (alice); } typedef struct { TpSubscriptionState subscribe; TpSubscriptionState publish; const gchar *publish_request; GMainLoop *loop; } SubscriptionStates; static void assert_subscription_states (TpContact *contact, SubscriptionStates *states) { g_assert_cmpint (tp_contact_get_subscribe_state (contact), ==, states->subscribe); g_assert_cmpint (tp_contact_get_publish_state (contact), ==, states->publish); g_assert_cmpstr (tp_contact_get_publish_request (contact), ==, states->publish_request); } static void subscription_states_changed_cb (TpContact *contact, TpSubscriptionState subscribe, TpSubscriptionState publish, const gchar *publish_request, SubscriptionStates *states) { assert_subscription_states (contact, states); g_main_loop_quit (states->loop); } static void test_subscription_states (Fixture *f, gconstpointer unused G_GNUC_UNUSED) { TpHandle alice_handle; TpContact *alice; TpTestsContactListManager *manager; TpContactFeature features[] = { TP_CONTACT_FEATURE_SUBSCRIPTION_STATES }; SubscriptionStates states = { TP_SUBSCRIPTION_STATE_NO, TP_SUBSCRIPTION_STATE_NO, "", f->result.loop }; manager = tp_tests_contacts_connection_get_contact_list_manager ( f->service_conn); alice_handle = tp_handle_ensure (f->service_repo, "alice", NULL, NULL); g_assert_cmpuint (alice_handle, !=, 0); tp_connection_get_contacts_by_handle (f->client_conn, 1, &alice_handle, G_N_ELEMENTS (features), features, by_handle_cb, &f->result, finish, NULL); g_main_loop_run (f->result.loop); g_assert_cmpuint (f->result.contacts->len, ==, 1); g_assert_cmpuint (f->result.invalid->len, ==, 0); g_assert_no_error (f->result.error); g_assert (g_ptr_array_index (f->result.contacts, 0) != NULL); alice = g_object_ref (g_ptr_array_index (f->result.contacts, 0)); g_assert_cmpuint (tp_contact_get_handle (alice), ==, alice_handle); g_assert_cmpstr (tp_contact_get_identifier (alice), ==, "alice"); assert_subscription_states (alice, &states); reset_result (&f->result); g_signal_connect (alice, "subscription-states-changed", G_CALLBACK (subscription_states_changed_cb), &states); /* Request subscription */ tp_tests_contact_list_manager_request_subscription (manager, 1, &alice_handle, ""); states.subscribe = TP_SUBSCRIPTION_STATE_ASK; g_main_loop_run (states.loop); /* Request again must re-emit the signal. Saying please this time will make * the request accepted and will ask for publish. */ tp_tests_contact_list_manager_request_subscription (manager, 1, &alice_handle, "please"); g_main_loop_run (states.loop); states.subscribe = TP_SUBSCRIPTION_STATE_YES; states.publish = TP_SUBSCRIPTION_STATE_ASK; states.publish_request = "automatic publish request"; g_main_loop_run (states.loop); /* Remove the contact */ tp_tests_contact_list_manager_remove (manager, 1, &alice_handle); states.subscribe = TP_SUBSCRIPTION_STATE_NO; states.publish = TP_SUBSCRIPTION_STATE_NO; states.publish_request = ""; g_main_loop_run (states.loop); g_object_unref (alice); } typedef struct { GPtrArray *groups; GMainLoop *loop; } ContactGroups; static void assert_contact_groups (TpContact *contact, ContactGroups *data) { const gchar * const *groups = tp_contact_get_contact_groups (contact); guint i; g_assert (groups != NULL); g_assert_cmpuint (g_strv_length ((GStrv) groups), ==, data->groups->len); for (i = 0; i < data->groups->len; i++) g_assert (tp_strv_contains (groups, g_ptr_array_index (data->groups, i))); } static void contact_groups_changed_cb (TpContact *contact, GStrv added, GStrv removed, ContactGroups *data) { assert_contact_groups (contact, data); g_main_loop_quit (data->loop); } static void test_contact_groups (Fixture *f, gconstpointer unused G_GNUC_UNUSED) { TpHandle alice_handle; TpContact *alice; TpTestsContactListManager *manager; TpContactFeature features[] = { TP_CONTACT_FEATURE_CONTACT_GROUPS }; ContactGroups data; data.groups = g_ptr_array_new (); data.loop = f->result.loop; manager = tp_tests_contacts_connection_get_contact_list_manager ( f->service_conn); alice_handle = tp_handle_ensure (f->service_repo, "alice", NULL, NULL); g_assert_cmpuint (alice_handle, !=, 0); tp_connection_get_contacts_by_handle (f->client_conn, 1, &alice_handle, G_N_ELEMENTS (features), features, by_handle_cb, &f->result, finish, NULL); g_main_loop_run (f->result.loop); g_assert_cmpuint (f->result.contacts->len, ==, 1); g_assert_cmpuint (f->result.invalid->len, ==, 0); g_assert_no_error (f->result.error); g_assert (g_ptr_array_index (f->result.contacts, 0) != NULL); alice = g_object_ref (g_ptr_array_index (f->result.contacts, 0)); g_assert_cmpuint (tp_contact_get_handle (alice), ==, alice_handle); g_assert_cmpstr (tp_contact_get_identifier (alice), ==, "alice"); assert_contact_groups (alice, &data); reset_result (&f->result); g_signal_connect (alice, "contact-groups-changed", G_CALLBACK (contact_groups_changed_cb), &data); g_ptr_array_add (data.groups, "group1"); tp_tests_contact_list_manager_add_to_group (manager, "group1", alice_handle); g_main_loop_run (data.loop); g_ptr_array_add (data.groups, "group2"); tp_tests_contact_list_manager_add_to_group (manager, "group2", alice_handle); g_main_loop_run (data.loop); g_ptr_array_remove_index_fast (data.groups, 0); tp_tests_contact_list_manager_remove_from_group (manager, "group1", alice_handle); g_main_loop_run (data.loop); g_ptr_array_set_size (data.groups, 0); g_ptr_array_add (data.groups, "group1"); g_ptr_array_add (data.groups, "group2"); g_ptr_array_add (data.groups, "group3"); tp_contact_set_contact_groups_async (alice, data.groups->len, (const gchar * const *) data.groups->pdata, NULL, NULL); g_main_loop_run (data.loop); g_ptr_array_unref (data.groups); g_object_unref (alice); } static void assert_no_location (TpContact *contact) { /* We could reasonably represent “no published location” as NULL or as an * empty a{sv}, so allow both. */ GHashTable *retrieved_location = tp_contact_get_location (contact); if (retrieved_location != NULL) g_assert (g_hash_table_size (retrieved_location) == 0); } /* This is a regression test for an issue where the LOCATION feature would * never be marked as prepared for contacts with no published location, so * repeated calls to tp_connection_get_contacts_by_handle() would call * GetContactAttributes() over and over. It's really a special case of * test_by_handle_again(), but presented separately for clarity. */ static void test_no_location (Fixture *f, gconstpointer unused G_GNUC_UNUSED) { TpHandle handle; TpContact *contact; gpointer weak_pointer; TpContactFeature feature = TP_CONTACT_FEATURE_LOCATION; GHashTable *norway = tp_asv_new ("country", G_TYPE_STRING, "Norway", NULL); notify_ctx notify_ctx_alice; GVariant *vardict; g_test_bug ("39377"); handle = tp_handle_ensure (f->service_repo, "alice", NULL, NULL); g_assert_cmpuint (handle, !=, 0); tp_connection_get_contacts_by_handle (f->client_conn, 1, &handle, 1, &feature, by_handle_cb, &f->result, finish, NULL); g_main_loop_run (f->result.loop); g_assert_cmpuint (f->result.contacts->len, ==, 1); g_assert_cmpuint (f->result.invalid->len, ==, 0); g_assert_no_error (f->result.error); g_assert (g_ptr_array_index (f->result.contacts, 0) != NULL); contact = g_object_ref (g_ptr_array_index (f->result.contacts, 0)); g_assert_cmpuint (tp_contact_get_handle (contact), ==, handle); assert_no_location (contact); reset_result (&f->result); /* Although Alice doesn't have a published location, the feature's still been * prepared, so we shouldn't need any D-Bus traffic to re-fetch her TpContact. */ make_the_connection_disappear (f); tp_connection_get_contacts_by_handle (f->client_conn, 1, &handle, 1, &feature, by_handle_cb, &f->result, finish, NULL); g_main_loop_run (f->result.loop); g_assert_no_error (f->result.error); g_assert_cmpuint (f->result.contacts->len, ==, 1); g_assert_cmpuint (f->result.invalid->len, ==, 0); g_assert (g_ptr_array_index (f->result.contacts, 0) == contact); assert_no_location (contact); put_the_connection_back (f); g_assert (f->result.error == NULL); reset_result (&f->result); /* Despite Alice not currently having a published location, we should * certainly be listening to changes to her location. */ notify_ctx_init (¬ify_ctx_alice); g_signal_connect (contact, "notify", G_CALLBACK (contact_notify_cb), ¬ify_ctx_alice); tp_tests_contacts_connection_change_locations (f->service_conn, 1, &handle, &norway); tp_tests_proxy_run_until_dbus_queue_processed (f->client_conn); g_assert (notify_ctx_alice.location_changed); g_assert (notify_ctx_alice.location_vardict_changed); vardict = tp_contact_dup_location (contact); ASSERT_SAME_LOCATION (tp_contact_get_location (contact), vardict, norway); g_variant_unref (vardict); weak_pointer = contact; g_object_add_weak_pointer ((GObject *) contact, &weak_pointer); g_object_unref (contact); g_assert (weak_pointer == NULL); /* Check that first retrieving a contact without the LOCATION feature, and * later upgrading it to have the LOCATION feature, does the right thing. */ handle = tp_handle_ensure (f->service_repo, "rupert", NULL, NULL); g_assert_cmpuint (handle, !=, 0); tp_tests_contacts_connection_change_locations (f->service_conn, 1, &handle, &norway); tp_connection_get_contacts_by_handle (f->client_conn, 1, &handle, 0, NULL, by_handle_cb, &f->result, finish, NULL); g_main_loop_run (f->result.loop); g_assert_cmpuint (f->result.contacts->len, ==, 1); g_assert_cmpuint (f->result.invalid->len, ==, 0); g_assert_no_error (f->result.error); g_assert (g_ptr_array_index (f->result.contacts, 0) != NULL); contact = g_object_ref (g_ptr_array_index (f->result.contacts, 0)); g_assert_cmpuint (tp_contact_get_handle (contact), ==, handle); assert_no_location (contact); /* clean up before doing the second request */ reset_result (&f->result); tp_connection_upgrade_contacts (f->client_conn, 1, &contact, 1, &feature, upgrade_cb, &f->result, finish, NULL); g_main_loop_run (f->result.loop); g_assert_no_error (f->result.error); g_assert_cmpuint (f->result.contacts->len, ==, 1); g_assert (g_ptr_array_index (f->result.contacts, 0) == contact); vardict = tp_contact_dup_location (contact); ASSERT_SAME_LOCATION (tp_contact_get_location (contact), vardict, norway); g_variant_unref (vardict); reset_result (&f->result); weak_pointer = contact; g_object_add_weak_pointer ((GObject *) contact, &weak_pointer); g_object_unref (contact); g_assert (weak_pointer == NULL); tp_tests_proxy_run_until_dbus_queue_processed (f->client_conn); } static void setup_broken_client_types_conn (Fixture *f, gconstpointer unused G_GNUC_UNUSED) { tp_tests_create_and_connect_conn ( TP_TESTS_TYPE_BROKEN_CLIENT_TYPES_CONNECTION, "me@test.com", &f->base_connection, &f->client_conn); f->service_conn = TP_TESTS_CONTACTS_CONNECTION (f->base_connection); g_object_ref (f->service_conn); f->service_repo = tp_base_connection_get_handles (f->base_connection, TP_HANDLE_TYPE_CONTACT); f->result.loop = g_main_loop_new (NULL, FALSE); } static void test_superfluous_attributes (Fixture *f, gconstpointer unused G_GNUC_UNUSED) { TpHandle handle; TpContact *contact; const gchar * const *client_types; TpContactFeature client_types_feature = TP_CONTACT_FEATURE_CLIENT_TYPES; TpContactFeature presence_feature = TP_CONTACT_FEATURE_PRESENCE; g_assert (TP_TESTS_IS_BROKEN_CLIENT_TYPES_CONNECTION (f->service_conn)); handle = tp_handle_ensure (f->service_repo, "helge", NULL, NULL); g_assert_cmpuint (handle, !=, 0); /* We ask for ClientTypes; the CM is broken and adds SimplePresence * information to the reply... it also omits the /client-types attribute from * the reply, which, since the spec says “Omitted from the result if the * contact's client types are not known.” leaves us in the exciting position * of having to decide between marking the feature as prepared anyway or * saying it failed, and also deciding whether get_client_types returns [] or * NULL... */ tp_connection_get_contacts_by_handle (f->client_conn, 1, &handle, 1, &client_types_feature, by_handle_cb, &f->result, finish, NULL); g_main_loop_run (f->result.loop); g_assert_cmpuint (f->result.contacts->len, ==, 1); g_assert_cmpuint (f->result.invalid->len, ==, 0); g_assert_no_error (f->result.error); g_assert (g_ptr_array_index (f->result.contacts, 0) != NULL); contact = g_object_ref (g_ptr_array_index (f->result.contacts, 0)); g_assert_cmpuint (tp_contact_get_handle (contact), ==, handle); /* She doesn't have any client types. There are two reasonable ways to * represent this. */ client_types = tp_contact_get_client_types (contact); if (client_types != NULL) g_assert_cmpstr (client_types[0], ==, NULL); /* She also shouldn't have any presence information, despite it being * inexplicably included in the GetContactAttributes reply. Specifically: * because we have not connected to PresencesChanged, it's not safe to just * randomly stash this information and mark the feature as prepared. * * (If we wanted to be really smart we could do something like: if the * information's there for some reason, and we happen already to be bound to * PresencesChanged due to preparing that feature on another contact … then * accept the mysterious information. But that seems fragile and prone to * people relying on sketchy behaviour.) */ g_assert_cmpstr (tp_contact_get_presence_message (contact), ==, ""); g_assert_cmpstr (tp_contact_get_presence_status (contact), ==, ""); g_assert_cmpuint (tp_contact_get_presence_type (contact), ==, TP_CONNECTION_PRESENCE_TYPE_UNSET); reset_result (&f->result); /* So now if we try to prepare TP_CONTACT_FEATURE_PRESENCE, we should need to * make some D-Bus calls: it shouldn't have been marked prepared by the * previous call. Successfully upgrading to this feature is tested * elsewhere, so we'll test that upgrading fails if the connection's * mysteriously died. */ make_the_connection_disappear (f); tp_connection_get_contacts_by_handle (f->client_conn, 1, &handle, 1, &presence_feature, by_handle_cb, &f->result, finish, NULL); g_main_loop_run (f->result.loop); /* Not gonna make any particular assertions about what the error is. */ g_assert_error (f->result.error, DBUS_GERROR, DBUS_GERROR_UNKNOWN_METHOD); put_the_connection_back (f); reset_result (&f->result); g_object_unref (contact); } static void contact_list_changed_cb (TpConnection *connection, GPtrArray *added, GPtrArray *removed, gpointer user_data) { gboolean *received = user_data; g_assert (added != NULL); g_assert (removed != NULL); *received = TRUE; } static void test_contact_list (Fixture *f, gconstpointer unused G_GNUC_UNUSED) { const GQuark conn_features[] = { TP_CONNECTION_FEATURE_CONTACT_LIST, 0 }; const GQuark feature_connected[] = { TP_CONNECTION_FEATURE_CONNECTED, 0 }; Result result = { g_main_loop_new (NULL, FALSE), NULL, NULL, NULL }; TpTestsContactListManager *manager; TpSimpleClientFactory *factory; const gchar *id = "contact-list-id"; const gchar *alias = "Contact List Alias"; const gchar *message = "I'm your best friend"; TpHandle handle; GPtrArray *contacts; TpContact *contact; gboolean got_contact_list_changed = FALSE; manager = tp_tests_contacts_connection_get_contact_list_manager ( f->service_conn); /* Connection is OFFLINE initially */ tp_tests_proxy_run_until_prepared (f->client_conn, conn_features); g_assert_cmpint (tp_connection_get_contact_list_state (f->client_conn), ==, TP_CONTACT_LIST_STATE_NONE); g_assert (tp_connection_get_contact_list_persists (f->client_conn)); g_assert (tp_connection_get_can_change_contact_list (f->client_conn)); g_assert (tp_connection_get_request_uses_message (f->client_conn)); /* Add a remote-pending contact in our roster CM-side */ handle = tp_handle_ensure (f->service_repo, id, NULL, NULL); tp_tests_contacts_connection_change_aliases (f->service_conn, 1, &handle, &alias); tp_tests_contact_list_manager_request_subscription (manager, 1, &handle, message); /* Tell connection's factory contact features we want */ factory = tp_proxy_get_factory (f->client_conn); tp_simple_client_factory_add_contact_features_varargs (factory, TP_CONTACT_FEATURE_ALIAS, TP_CONTACT_FEATURE_AVATAR_DATA, TP_CONTACT_FEATURE_INVALID); /* Now put it online and wait for contact list state move to success */ g_signal_connect (f->client_conn, "contact-list-changed", G_CALLBACK (contact_list_changed_cb), &got_contact_list_changed); g_signal_connect_swapped (f->client_conn, "notify::contact-list-state", G_CALLBACK (finish), &result); tp_cli_connection_call_connect (f->client_conn, -1, NULL, NULL, NULL, NULL); tp_tests_proxy_run_until_prepared (f->client_conn, feature_connected); g_assert_cmpint (tp_connection_get_contact_list_state (f->client_conn), ==, TP_CONTACT_LIST_STATE_SUCCESS); /* SUCCESS state must have been delayed until TpContact is prepared, and contact-list-changed must have been emitted just before */ g_assert (got_contact_list_changed); contacts = tp_connection_dup_contact_list (f->client_conn); g_assert (contacts != NULL); g_assert_cmpint (contacts->len, ==, 1); contact = g_ptr_array_index (contacts, 0); g_assert (contact != NULL); g_assert_cmpstr (tp_contact_get_identifier (contact), ==, id); g_assert_cmpstr (tp_contact_get_alias (contact), ==, alias); /* Even if we didn't explicitely asked that feature, we should have it for free */ g_assert (tp_contact_has_feature (contact, TP_CONTACT_FEATURE_SUBSCRIPTION_STATES)); g_assert_cmpint (tp_contact_get_subscribe_state (contact), ==, TP_SUBSCRIPTION_STATE_ASK); /* We asked for AVATAR_DATA, verify we got it. This is special because it has * no contact attribute, and ContactList preparation does not go through * the slow path. */ g_assert (tp_contact_has_feature (contact, TP_CONTACT_FEATURE_AVATAR_DATA)); g_ptr_array_unref (contacts); } typedef struct { Fixture *f; GMainLoop *loop; gboolean expecting_signal; gboolean got_stored; gboolean got_publish; gboolean got_subscribe; } MembersChangedClosure; static void channel_prepared_cb (GObject *source_object, GAsyncResult *res, gpointer user_data) { TpChannel *channel = TP_CHANNEL (source_object); MembersChangedClosure *closure = user_data; const gchar *identifier; GError *error = NULL; tp_proxy_prepare_finish (channel, res, &error); g_assert_no_error (error); g_assert (closure->expecting_signal); /* We expect to just get the stored, publish and subscribe lists exactly * once */ identifier = tp_channel_get_identifier (channel); if (g_strcmp0 (identifier, "stored") == 0) { g_assert (!closure->got_stored); closure->got_stored = TRUE; } else if (g_strcmp0 (identifier, "publish") == 0) { g_assert (!closure->got_publish); closure->got_publish = TRUE; } else if (g_strcmp0 (identifier, "subscribe") == 0) { g_assert (!closure->got_subscribe); closure->got_subscribe = TRUE; } else { g_assert_not_reached (); } if (closure->got_stored && closure->got_publish && closure->got_subscribe) g_main_loop_quit (closure->loop); } static DBusHandlerResult message_filter (DBusConnection *connection, DBusMessage *msg, gpointer user_data) { MembersChangedClosure *closure = user_data; if (dbus_message_is_signal (msg, TP_IFACE_CHANNEL_INTERFACE_GROUP, "MembersChanged")) { TpChannel *channel; DBusMessageIter iter, sub_iter; gint type; dbus_int32_t *added; gint n_added; channel = tp_channel_new (closure->f->client_conn, dbus_message_get_path (msg), TP_IFACE_CHANNEL_INTERFACE_GROUP, TP_HANDLE_TYPE_LIST, 0, NULL); tp_proxy_prepare_async (channel, NULL, channel_prepared_cb, closure); g_object_unref (channel); /* Extract the number of added handles */ dbus_message_iter_init (msg, &iter); dbus_message_iter_next (&iter); /* Skipe the message */ type = dbus_message_iter_get_arg_type (&iter); g_assert_cmpint (type, ==, DBUS_TYPE_ARRAY); dbus_message_iter_recurse (&iter, &sub_iter); type = dbus_message_iter_get_arg_type (&sub_iter); g_assert_cmpint (type, ==, DBUS_TYPE_UINT32); dbus_message_iter_get_fixed_array (&sub_iter, &added, &n_added); /* Bob, Alice and Carol == 3 */ g_assert_cmpint (n_added, ==, 3); } return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; } static void test_initial_contact_list (Fixture *f, gconstpointer unused G_GNUC_UNUSED) { const GQuark conn_features[] = { TP_CONNECTION_FEATURE_CONTACT_LIST, 0 }; const GQuark feature_connected[] = { TP_CONNECTION_FEATURE_CONNECTED, 0 }; TpTestsContactListManager *manager; MembersChangedClosure closure; DBusConnection *dbus_connection; TpHandle alice; const gchar *alice_id = "alice"; TpHandle bob_carol[2]; const gchar *bob_id = "bob"; const gchar *carol_id = "carol"; manager = tp_tests_contacts_connection_get_contact_list_manager ( f->service_conn); /* We use a filter to be sure not to miss the initial MembersChanged * signals */ closure.f = f; closure.loop = g_main_loop_new (NULL, FALSE); closure.got_stored = FALSE; closure.got_publish = FALSE; closure.got_subscribe = FALSE; /* No signal is expected until after we connect */ closure.expecting_signal = FALSE; dbus_connection = dbus_g_connection_get_connection ( tp_proxy_get_dbus_connection (TP_PROXY (f->client_conn))); dbus_connection_ref (dbus_connection); dbus_connection_add_filter (dbus_connection, message_filter, &closure, NULL); dbus_bus_add_match (dbus_connection, MEMBERS_CHANGED_MATCH_RULE, NULL); /* Connection is OFFLINE initially */ tp_tests_proxy_run_until_prepared (f->client_conn, conn_features); g_assert_cmpint (tp_connection_get_contact_list_state (f->client_conn), ==, TP_CONTACT_LIST_STATE_NONE); /* Add contacts in our initial roster CM-side */ alice = tp_handle_ensure (f->service_repo, alice_id, NULL, NULL); tp_tests_contact_list_manager_add_initial_contacts (manager, 1, &alice); bob_carol[0] = tp_handle_ensure (f->service_repo, bob_id, NULL, NULL); bob_carol[1] = tp_handle_ensure (f->service_repo, carol_id, NULL, NULL); tp_tests_contact_list_manager_add_initial_contacts (manager, 2, bob_carol); /* Now put it online and wait for the contact list state to move to * success */ closure.expecting_signal = TRUE; tp_cli_connection_call_connect (f->client_conn, -1, NULL, NULL, NULL, NULL); tp_tests_proxy_run_until_prepared (f->client_conn, feature_connected); dbus_bus_remove_match (dbus_connection, MEMBERS_CHANGED_MATCH_RULE, NULL); dbus_connection_remove_filter (dbus_connection, message_filter, &closure); dbus_connection_unref (dbus_connection); g_main_loop_unref (closure.loop); } static void test_self_contact (Fixture *f, gconstpointer unused G_GNUC_UNUSED) { const GQuark conn_features[] = { TP_CONNECTION_FEATURE_CONNECTED, 0 }; TpSimpleClientFactory *factory; TpContact *contact; factory = tp_proxy_get_factory (f->client_conn); tp_simple_client_factory_add_contact_features_varargs (factory, TP_CONTACT_FEATURE_ALIAS, TP_CONTACT_FEATURE_INVALID); tp_cli_connection_call_connect (f->client_conn, -1, NULL, NULL, NULL, NULL); tp_tests_proxy_run_until_prepared (f->client_conn, conn_features); contact = tp_connection_get_self_contact (f->client_conn); g_assert (contact != NULL); g_assert (tp_contact_has_feature (contact, TP_CONTACT_FEATURE_ALIAS)); } static void setup_internal (Fixture *f, gboolean connect, gconstpointer user_data) { tp_tests_create_conn (TP_TESTS_TYPE_CONTACTS_CONNECTION, "me@test.com", connect, &f->base_connection, &f->client_conn); f->service_conn = TP_TESTS_CONTACTS_CONNECTION (f->base_connection); g_object_ref (f->service_conn); tp_tests_create_conn (TP_TESTS_TYPE_LEGACY_CONTACTS_CONNECTION, "me2@test.com", connect, &f->legacy_base_connection, &f->legacy_client_conn); tp_tests_create_conn (TP_TESTS_TYPE_NO_REQUESTS_CONNECTION, "me3@test.com", connect, &f->no_requests_base_connection, &f->no_requests_client_conn); f->service_repo = tp_base_connection_get_handles (f->base_connection, TP_HANDLE_TYPE_CONTACT); f->result.loop = g_main_loop_new (NULL, FALSE); } static void setup (Fixture *f, gconstpointer user_data) { setup_internal (f, TRUE, user_data); } static void setup_no_connect (Fixture *f, gconstpointer user_data) { setup_internal (f, FALSE, user_data); } static void teardown (Fixture *f, gconstpointer unused G_GNUC_UNUSED) { if (f->client_conn != NULL) { TpConnection *conn = f->client_conn; g_object_add_weak_pointer ((GObject *) conn, (gpointer *) &conn); tp_tests_connection_assert_disconnect_succeeds (conn); g_object_unref (conn); g_assert (conn == NULL); f->client_conn = NULL; } f->service_repo = NULL; tp_clear_object (&f->service_conn); tp_clear_object (&f->base_connection); if (f->legacy_client_conn != NULL) tp_tests_connection_assert_disconnect_succeeds (f->legacy_client_conn); tp_clear_object (&f->legacy_client_conn); tp_clear_object (&f->legacy_base_connection); if (f->no_requests_client_conn != NULL) { tp_tests_connection_assert_disconnect_succeeds ( f->no_requests_client_conn); } tp_clear_object (&f->no_requests_client_conn); tp_clear_object (&f->no_requests_base_connection); reset_result (&f->result); tp_clear_pointer (&f->result.loop, g_main_loop_unref); } int main (int argc, char **argv) { gint ret; gchar *dir; GError *error = NULL; tp_tests_init (&argc, &argv); g_test_bug_base ("http://bugs.freedesktop.org/show_bug.cgi?id="); /* Make sure g_get_user_cache_dir() returns a tmp directory, to not mess up * user's cache dir. */ dir = g_dir_make_tmp ("tp-glib-tests-XXXXXX", &error); g_assert_no_error (error); g_setenv ("XDG_CACHE_HOME", dir, TRUE); g_assert_cmpstr (g_get_user_cache_dir (), ==, dir); #define ADD(x) \ g_test_add ("/contacts/" #x, Fixture, NULL, setup, test_ ## x, teardown) ADD (by_handle); ADD (by_handle_again); ADD (by_handle_upgrade); ADD (no_features); ADD (features); ADD (upgrade); ADD (upgrade_noop); ADD (by_id); ADD (avatar_requirements); ADD (avatar_data); ADD (avatar_data_after_token); ADD (contact_info); ADD (dup_if_possible); ADD (subscription_states); ADD (contact_groups); /* test if TpContact fallbacks to connection's capabilities if * ContactCapabilities is not implemented. */ ADD (capabilities_without_contact_caps); /* test if TP_CONTACT_FEATURE_CAPABILITIES is prepared but with * an empty set of capabilities if the connection doesn't support * ContactCapabilities and Requests. */ ADD (prepare_contact_caps_without_request); ADD (no_location); g_test_add ("/contacts/superfluous-attributes", Fixture, NULL, setup_broken_client_types_conn, test_superfluous_attributes, teardown); g_test_add ("/contacts/contact-list", Fixture, NULL, setup_no_connect, test_contact_list, teardown); g_test_add ("/contacts/initial-contact-list", Fixture, NULL, setup_no_connect, test_initial_contact_list, teardown); g_test_add ("/contacts/self-contact", Fixture, NULL, setup_no_connect, test_self_contact, teardown); ret = tp_tests_run_with_bus (); g_assert (haze_remove_directory (dir)); g_free (dir); return ret; }