From fc14c5fd0b19c344717e3c01898224dbe70bd507 Mon Sep 17 00:00:00 2001 From: Marc Alexander Date: Tue, 27 Feb 2024 20:43:27 +0100 Subject: [PATCH] [ticket/17010] Increase test coverage PHPBB3-17010 --- phpBB/phpbb/notification/method/webpush.php | 69 +++++++- .../webpush_notification.type.post.xml | 6 + .../notification_method_webpush_test.php | 147 ++++++++++++++++-- 3 files changed, 204 insertions(+), 18 deletions(-) diff --git a/phpBB/phpbb/notification/method/webpush.php b/phpBB/phpbb/notification/method/webpush.php index 386dfd2525..c71256a29d 100644 --- a/phpBB/phpbb/notification/method/webpush.php +++ b/phpBB/phpbb/notification/method/webpush.php @@ -239,12 +239,10 @@ class webpush extends messenger_base implements extended_method_interface } // Remove any subscriptions that couldn't be queued, i.e. that have invalid data - if (count($remove_subscriptions)) - { - $sql = 'DELETE FROM ' . $this->push_subscriptions_table . ' - WHERE ' . $this->db->sql_in_set('subscription_id', $remove_subscriptions); - $this->db->sql_query($sql); - } + $this->remove_subscriptions($remove_subscriptions); + + // List to fill with expired subscriptions based on return + $expired_endpoints = []; try { @@ -252,8 +250,16 @@ class webpush extends messenger_base implements extended_method_interface { if (!$report->isSuccess()) { - $report_data = \phpbb\json\sanitizer::sanitize($report->jsonSerialize()); - $this->log->add('admin', ANONYMOUS, '', 'LOG_WEBPUSH_MESSAGE_FAIL', false, [$report_data['reason']]); + // Fill array of endpoints to remove if subscription has expired + if ($report->isSubscriptionExpired()) + { + $expired_endpoints = $report->getEndpoint(); + } + else + { + $report_data = \phpbb\json\sanitizer::sanitize($report->jsonSerialize()); + $this->log->add('admin', ANONYMOUS, '', 'LOG_WEBPUSH_MESSAGE_FAIL', false, [$report_data['reason']]); + } } } } @@ -262,6 +268,8 @@ class webpush extends messenger_base implements extended_method_interface $this->log->add('critical', ANONYMOUS, '', 'LOG_WEBPUSH_MESSAGE_FAIL', false, [$exception->getMessage()]); } + $this->clean_expired_subscriptions($user_subscription_map, $expired_endpoints); + // We're done, empty the queue $this->empty_queue(); } @@ -373,4 +381,49 @@ class webpush extends messenger_base implements extended_method_interface return $user_subscription_map; } + + /** + * Remove subscriptions + * + * @param array $subscription_ids Subscription ids to remove + * @return void + */ + public function remove_subscriptions(array $subscription_ids): void + { + if (count($subscription_ids)) + { + $sql = 'DELETE FROM ' . $this->push_subscriptions_table . ' + WHERE ' . $this->db->sql_in_set('subscription_id', $subscription_ids); + $this->db->sql_query($sql); + } + } + + /** + * Clean expired subscriptions from the database + * + * @param array $user_subscription_map User subscription map + * @param array $expired_endpoints Expired endpoints + * @return void + */ + protected function clean_expired_subscriptions(array $user_subscription_map, array $expired_endpoints): void + { + if (!count($expired_endpoints)) + { + return; + } + + $remove_subscriptions = []; + foreach ($expired_endpoints as $endpoint) + { + foreach ($user_subscription_map as $user_id => $subscriptions) + { + if (isset($subscriptions['endpoint']) && $subscriptions['endpoint'] == $endpoint) + { + $remove_subscriptions[] = $subscriptions[$endpoint]['subscription_id']; + } + } + } + + $this->remove_subscriptions($remove_subscriptions); + } } diff --git a/tests/notification/fixtures/webpush_notification.type.post.xml b/tests/notification/fixtures/webpush_notification.type.post.xml index 7654ed30aa..896df4e1c1 100644 --- a/tests/notification/fixtures/webpush_notification.type.post.xml +++ b/tests/notification/fixtures/webpush_notification.type.post.xml @@ -133,6 +133,12 @@ username_clean user_permissions user_sig + + 1 + Anonymous + + + 2 poster diff --git a/tests/notification/notification_method_webpush_test.php b/tests/notification/notification_method_webpush_test.php index 0faff80576..3a0a48064c 100644 --- a/tests/notification/notification_method_webpush_test.php +++ b/tests/notification/notification_method_webpush_test.php @@ -32,6 +32,12 @@ class notification_method_webpush_test extends phpbb_tests_notification_base /** @var webpush */ protected $notification_method_webpush; + /** @var \phpbb\language\language */ + protected $language; + + /** @var \phpbb\log\log_interface */ + protected $log; + public function getDataSet() { return $this->createXMLDataSet(__DIR__ . '/fixtures/webpush_notification.type.post.xml'); @@ -97,9 +103,11 @@ class notification_method_webpush_test extends phpbb_tests_notification_base 'webpush_vapid_private' => self::VAPID_KEYS['privateKey'], ]); $lang_loader = new \phpbb\language\language_file_loader($phpbb_root_path, $phpEx); - $language = new \phpbb\language\language($lang_loader); - $user = new \phpbb\user($language, '\phpbb\datetime'); + $this->language = new \phpbb\language\language($lang_loader); + $this->language->add_lang('acp/common'); + $user = new \phpbb\user($this->language, '\phpbb\datetime'); $this->user = $user; + $this->user->data['user_options'] = 230271; $this->user_loader = new \phpbb\user_loader($avatar_helper, $this->db, $phpbb_root_path, $phpEx, 'phpbb_users'); $auth = $this->auth = new phpbb_mock_notifications_auth(); $this->phpbb_dispatcher = new phpbb_mock_event_dispatcher(); @@ -113,20 +121,22 @@ class notification_method_webpush_test extends phpbb_tests_notification_base $phpbb_root_path, $phpEx ); - $phpbb_log = new \phpbb\log\dummy(); + + $log_table = 'phpbb_log'; + $this->log = new \phpbb\log\log($this->db, $user, $auth, $this->phpbb_dispatcher, $phpbb_root_path, 'adm/', $phpEx, $log_table); $phpbb_container = $this->container = new ContainerBuilder(); $loader = new YamlFileLoader($phpbb_container, new FileLocator(__DIR__ . '/fixtures')); $loader->load('services_notification.yml'); $phpbb_container->set('user_loader', $this->user_loader); $phpbb_container->set('user', $user); - $phpbb_container->set('language', $language); + $phpbb_container->set('language', $this->language); $phpbb_container->set('config', $this->config); $phpbb_container->set('dbal.conn', $this->db); $phpbb_container->set('auth', $auth); $phpbb_container->set('cache.driver', $cache_driver); $phpbb_container->set('cache', $cache); - $phpbb_container->set('log', $phpbb_log); + $phpbb_container->set('log', $this->log); $phpbb_container->set('text_formatter.utils', new \phpbb\textformatter\s9e\utils()); $phpbb_container->set('dispatcher', $this->phpbb_dispatcher); $phpbb_container->setParameter('core.root_path', $phpbb_root_path); @@ -158,7 +168,7 @@ class notification_method_webpush_test extends phpbb_tests_notification_base $collection->add('ban.type.email'); $collection->add('ban.type.user'); $collection->add('ban.type.ip'); - $ban_manager = new \phpbb\ban\manager($collection, new \phpbb\cache\driver\dummy(), $this->db, $language, $phpbb_log, $user, 'phpbb_bans', 'phpbb_users'); + $ban_manager = new \phpbb\ban\manager($collection, new \phpbb\cache\driver\dummy(), $this->db, $this->language, $this->log, $user, 'phpbb_bans', 'phpbb_users'); $phpbb_container->set('ban.manager', $ban_manager); $this->notification_method_webpush = new \phpbb\notification\method\webpush( @@ -183,7 +193,7 @@ class notification_method_webpush_test extends phpbb_tests_notification_base $this->phpbb_dispatcher, $this->db, $this->cache, - $language, + $this->language, $this->user, 'phpbb_notification_types', 'phpbb_user_notifications' @@ -400,7 +410,122 @@ class notification_method_webpush_test extends phpbb_tests_notification_base } } - protected function create_subscription_for_user($user_id): array + /** + * @dataProvider data_notification_webpush + */ + public function test_notify_empty_queue($notification_type, $post_data, $expected_users): void + { + foreach ($expected_users as $user_id => $user_data) + { + $this->create_subscription_for_user($user_id); + } + + $post_data = array_merge([ + 'post_time' => 1349413322, + 'poster_id' => 1, + 'topic_title' => '', + 'post_subject' => '', + 'post_username' => '', + 'forum_name' => '', + ], + + $post_data); + $notification_options = [ + 'item_id' => $post_data['post_id'], + 'item_parent_id' => $post_data['topic_id'], + ]; + + $notified_users = $this->notification_method_webpush->get_notified_users($this->notifications->get_notification_type_id($notification_type), $notification_options); + $this->assertEquals(0, count($notified_users), 'Assert no user has been notified yet'); + + $this->notifications->add_notifications($notification_type, $post_data); + + $notified_users = $this->notification_method_webpush->get_notified_users($this->notifications->get_notification_type_id($notification_type), $notification_options); + $this->assertEquals($expected_users, $notified_users, 'Assert that expected users have been notified'); + + $post_data['post_id']++; + $notification_options['item_id'] = $post_data['post_id']; + $post_data['post_time'] = 1349413323; + + $this->notifications->add_notifications($notification_type, $post_data); + + $notified_users2 = $this->notification_method_webpush->get_notified_users($this->notifications->get_notification_type_id($notification_type), $notification_options); + $this->assertEquals($expected_users, $notified_users2, 'Assert that expected users stay the same after replying to same topic'); + } + + /** + * @dataProvider data_notification_webpush + */ + public function test_notify_invalid_endpoint($notification_type, $post_data, $expected_users): void + { + $subscription_info = []; + foreach ($expected_users as $user_id => $user_data) + { + $subscription_info[$user_id][] = $this->create_subscription_for_user($user_id); + } + + // Create second subscription for first user ID passed + if (count($expected_users)) + { + $first_user_id = array_key_first($expected_users); + $first_user_sub = $this->create_subscription_for_user($first_user_id, true); + $subscription_info[$first_user_id][] = $first_user_sub; + } + + $post_data = array_merge([ + 'post_time' => 1349413322, + 'poster_id' => 1, + 'topic_title' => '', + 'post_subject' => '', + 'post_username' => '', + 'forum_name' => '', + ], + + $post_data); + $notification_options = [ + 'item_id' => $post_data['post_id'], + 'item_parent_id' => $post_data['topic_id'], + ]; + + $notified_users = $this->notification_method_webpush->get_notified_users($this->notifications->get_notification_type_id($notification_type), $notification_options); + $this->assertEquals(0, count($notified_users), 'Assert no user has been notified yet'); + + foreach ($expected_users as $user_id => $data) + { + $messages = $this->get_messages_for_subscription($subscription_info[$user_id][0]['clientHash']); + $this->assertEmpty($messages); + } + + $this->notifications->add_notifications($notification_type, $post_data); + + $notified_users = $this->notification_method_webpush->get_notified_users($this->notifications->get_notification_type_id($notification_type), $notification_options); + $this->assertEquals($expected_users, $notified_users, 'Assert that expected users have been notified'); + + foreach ($expected_users as $user_id => $data) + { + $messages = $this->get_messages_for_subscription($subscription_info[$user_id][0]['clientHash']); + $this->assertNotEmpty($messages); + } + + if (isset($first_user_sub)) + { + $admin_logs = $this->log->get_logs('admin'); + $this->db->sql_query('DELETE FROM phpbb_log'); // Clear logs + $this->assertCount(1, $admin_logs, 'Assert that an admin log was created for invalid endpoint'); + + $log_entry = $admin_logs[0]; + + $this->assertStringStartsWith('Web Push message could not be sent:', $log_entry['action']); + $this->assertStringContainsString('400', $log_entry['action']); + } + } + + public function test_get_type(): void + { + $this->assertEquals('notification.method.webpush', $this->notification_method_webpush->get_type()); + } + + protected function create_subscription_for_user($user_id, bool $invalidate_endpoint = false): array { $client = new \GuzzleHttp\Client(); try @@ -420,8 +545,10 @@ class notification_method_webpush_test extends phpbb_tests_notification_base $this->assertStringStartsWith('http://localhost:9012/notify/', $subscription_data['endpoint']); $this->assertIsArray($subscription_data['keys']); - // Add subscription data to admin user (user id 2) - + if ($invalidate_endpoint) + { + $subscription_data['endpoint'] .= 'invalid'; + } $push_subscriptions_table = $this->container->getParameter('tables.push_subscriptions');