14bf952c9SAnna Dabrowska<?php 24bf952c9SAnna Dabrowska 34bf952c9SAnna Dabrowskanamespace dokuwiki\plugin\acknowledge\test; 44bf952c9SAnna Dabrowska 54bf952c9SAnna Dabrowskause DokuWikiTest; 64bf952c9SAnna Dabrowskause dokuwiki\Extension\Event; 74bf952c9SAnna Dabrowska 84bf952c9SAnna Dabrowska/** 94bf952c9SAnna Dabrowska * Tests for the notification plugin integration (action/notification.php). 104bf952c9SAnna Dabrowska * 114bf952c9SAnna Dabrowska * The notification plugin is pull-based: it fires PLUGIN_NOTIFICATION_GATHER per user and 124bf952c9SAnna Dabrowska * deduplicates the returned notifications by (plugin, id, user) permanently. These tests drive 134bf952c9SAnna Dabrowska * the gather handler directly and assert on the produced notification ids, which encode the 144bf952c9SAnna Dabrowska * "$page:$lastmod" semantics that define when a user is (re-)notified. 154bf952c9SAnna Dabrowska * 164bf952c9SAnna Dabrowska * @group plugin_acknowledge 174bf952c9SAnna Dabrowska * @group plugins 184bf952c9SAnna Dabrowska */ 194bf952c9SAnna Dabrowskaclass NotificationIntegrationTest extends DokuWikiTest 204bf952c9SAnna Dabrowska{ 214bf952c9SAnna Dabrowska /** @var array */ 224bf952c9SAnna Dabrowska protected $pluginsEnabled = ['acknowledge', 'sqlite', 'approve']; 234bf952c9SAnna Dabrowska 244bf952c9SAnna Dabrowska /** @var \helper_plugin_acknowledge */ 254bf952c9SAnna Dabrowska protected $helper; 264bf952c9SAnna Dabrowska 274bf952c9SAnna Dabrowska /** @var \dokuwiki\plugin\sqlite\SQLiteDB */ 284bf952c9SAnna Dabrowska protected $db; 294bf952c9SAnna Dabrowska 304bf952c9SAnna Dabrowska /** @var \helper_plugin_approve_db */ 314bf952c9SAnna Dabrowska protected $approve; 324bf952c9SAnna Dabrowska 334bf952c9SAnna Dabrowska public static function setUpBeforeClass(): void 344bf952c9SAnna Dabrowska { 354bf952c9SAnna Dabrowska parent::setUpBeforeClass(); 364bf952c9SAnna Dabrowska /** @var \auth_plugin_authplain $auth */ 374bf952c9SAnna Dabrowska global $auth; 384bf952c9SAnna Dabrowska $auth->createUser('alice', 'none', 'alice', 'alice@example.com', ['staff']); 394bf952c9SAnna Dabrowska } 404bf952c9SAnna Dabrowska 414bf952c9SAnna Dabrowska public function setUp(): void 424bf952c9SAnna Dabrowska { 434bf952c9SAnna Dabrowska parent::setUp(); 444bf952c9SAnna Dabrowska 454bf952c9SAnna Dabrowska // setApprovedStatus() records the approving user from $INFO 464bf952c9SAnna Dabrowska global $INFO; 474bf952c9SAnna Dabrowska $INFO['client'] = 'someapprover'; 484bf952c9SAnna Dabrowska 494bf952c9SAnna Dabrowska $this->helper = plugin_load('helper', 'acknowledge'); 504bf952c9SAnna Dabrowska $this->db = $this->helper->getDB(); 514bf952c9SAnna Dabrowska /** @var \helper_plugin_approve_db approve */ 524bf952c9SAnna Dabrowska $this->approve = plugin_load('helper', 'approve_db'); 534bf952c9SAnna Dabrowska $this->approve->addMaintainer('approved:**', 'someapprover'); 544bf952c9SAnna Dabrowska 55*b3fbf6ceSAnna Dabrowska // due (never acked), current (acked >= lastmod), outdated (acked < lastmod). 56*b3fbf6ceSAnna Dabrowska // These are virtual pages with no changelog, so storePageDate() simply records the lastmod. 57*b3fbf6ceSAnna Dabrowska $this->helper->storePageDate('wiki:due_new', 1000, ''); 58*b3fbf6ceSAnna Dabrowska $this->helper->storePageDate('wiki:done', 1000, ''); 59*b3fbf6ceSAnna Dabrowska $this->helper->storePageDate('wiki:outdated', 2000, ''); 604bf952c9SAnna Dabrowska 61*b3fbf6ceSAnna Dabrowska $this->helper->setPageAssignees('wiki:due_new', '@staff'); 62*b3fbf6ceSAnna Dabrowska $this->helper->setPageAssignees('wiki:done', '@staff'); 63*b3fbf6ceSAnna Dabrowska $this->helper->setPageAssignees('wiki:outdated', '@staff'); 644bf952c9SAnna Dabrowska 65*b3fbf6ceSAnna Dabrowska $this->helper->importAcknowledgement('wiki:done', 'alice', 2000); 66*b3fbf6ceSAnna Dabrowska $this->helper->importAcknowledgement('wiki:outdated', 'alice', 1000); 674bf952c9SAnna Dabrowska } 684bf952c9SAnna Dabrowska 694bf952c9SAnna Dabrowska /** 704bf952c9SAnna Dabrowska * Invoke the real gather handler for a user and return the produced notifications. 714bf952c9SAnna Dabrowska * 724bf952c9SAnna Dabrowska * @param string $user 734bf952c9SAnna Dabrowska * @return array 744bf952c9SAnna Dabrowska */ 754bf952c9SAnna Dabrowska protected function gather($user) 764bf952c9SAnna Dabrowska { 774bf952c9SAnna Dabrowska $data = [ 784bf952c9SAnna Dabrowska 'plugins' => ['acknowledge'], 794bf952c9SAnna Dabrowska 'user' => $user, 804bf952c9SAnna Dabrowska 'notifications' => [], 814bf952c9SAnna Dabrowska ]; 824bf952c9SAnna Dabrowska $event = new Event('PLUGIN_NOTIFICATION_GATHER', $data); 834bf952c9SAnna Dabrowska 844bf952c9SAnna Dabrowska /** @var \action_plugin_acknowledge_notification $action */ 854bf952c9SAnna Dabrowska $action = plugin_load('action', 'acknowledge_notification'); 864bf952c9SAnna Dabrowska $action->gatherNotifications($event); 874bf952c9SAnna Dabrowska 884bf952c9SAnna Dabrowska return $data['notifications']; 894bf952c9SAnna Dabrowska } 904bf952c9SAnna Dabrowska 914bf952c9SAnna Dabrowska /** 924bf952c9SAnna Dabrowska * Due and outdated pages are notified; up-to-date pages are not. The id is "$page:$lastmod". 934bf952c9SAnna Dabrowska */ 944bf952c9SAnna Dabrowska public function testDueAndOutdatedNotifiedCurrentNot() 954bf952c9SAnna Dabrowska { 964bf952c9SAnna Dabrowska $ids = array_column($this->gather('alice'), 'id'); 974bf952c9SAnna Dabrowska sort($ids); 984bf952c9SAnna Dabrowska 994bf952c9SAnna Dabrowska $this->assertEquals(['wiki:due_new:1000', 'wiki:outdated:2000'], $ids); 1004bf952c9SAnna Dabrowska } 1014bf952c9SAnna Dabrowska 1024bf952c9SAnna Dabrowska /** 1034bf952c9SAnna Dabrowska * The notification carries the assigned plugin name and a rendered link. 1044bf952c9SAnna Dabrowska */ 1054bf952c9SAnna Dabrowska public function testNotificationShape() 1064bf952c9SAnna Dabrowska { 1074bf952c9SAnna Dabrowska $notifications = $this->gather('alice'); 1084bf952c9SAnna Dabrowska $this->assertNotEmpty($notifications); 1094bf952c9SAnna Dabrowska 1104bf952c9SAnna Dabrowska $notification = $notifications[0]; 1114bf952c9SAnna Dabrowska $this->assertEquals('acknowledge', $notification['plugin']); 1124bf952c9SAnna Dabrowska $this->assertStringContainsString('href', $notification['full']); 1134bf952c9SAnna Dabrowska $this->assertIsInt($notification['timestamp']); 1144bf952c9SAnna Dabrowska } 1154bf952c9SAnna Dabrowska 1164bf952c9SAnna Dabrowska /** 1174bf952c9SAnna Dabrowska * A page edit (new lastmod) yields a new id, so the user is re-notified after re-ack falls due. 1184bf952c9SAnna Dabrowska */ 1194bf952c9SAnna Dabrowska public function testPageChangeProducesNewId() 1204bf952c9SAnna Dabrowska { 1214bf952c9SAnna Dabrowska $before = array_column($this->gather('alice'), 'id'); 1224bf952c9SAnna Dabrowska $this->assertContains('wiki:due_new:1000', $before); 1234bf952c9SAnna Dabrowska 1244bf952c9SAnna Dabrowska // simulate a page edit bumping the stored modification date 125*b3fbf6ceSAnna Dabrowska $this->helper->storePageDate('wiki:due_new', 1500, 'edited'); 1264bf952c9SAnna Dabrowska 1274bf952c9SAnna Dabrowska $after = array_column($this->gather('alice'), 'id'); 1284bf952c9SAnna Dabrowska $this->assertContains('wiki:due_new:1500', $after); 1294bf952c9SAnna Dabrowska $this->assertNotContains('wiki:due_new:1000', $after); 1304bf952c9SAnna Dabrowska } 1314bf952c9SAnna Dabrowska 1324bf952c9SAnna Dabrowska /** 1334bf952c9SAnna Dabrowska * Changing assignment rules (without a page edit) keeps the same id, so dedup prevents re-notify. 1344bf952c9SAnna Dabrowska */ 1354bf952c9SAnna Dabrowska public function testAssignmentChurnKeepsSameId() 1364bf952c9SAnna Dabrowska { 1374bf952c9SAnna Dabrowska $before = array_column($this->gather('alice'), 'id'); 1384bf952c9SAnna Dabrowska $this->assertContains('wiki:due_new:1000', $before); 1394bf952c9SAnna Dabrowska 1404bf952c9SAnna Dabrowska // rule churn: widen the assignees but leave lastmod untouched 141*b3fbf6ceSAnna Dabrowska $this->helper->setPageAssignees('wiki:due_new', '@staff,@other'); 1424bf952c9SAnna Dabrowska 1434bf952c9SAnna Dabrowska $after = array_column($this->gather('alice'), 'id'); 1444bf952c9SAnna Dabrowska $this->assertContains('wiki:due_new:1000', $after); 1454bf952c9SAnna Dabrowska } 1464bf952c9SAnna Dabrowska 1474bf952c9SAnna Dabrowska /** 1484bf952c9SAnna Dabrowska * With the integration disabled, the gather handler produces nothing. 1494bf952c9SAnna Dabrowska */ 1504bf952c9SAnna Dabrowska public function testDisabledIntegrationProducesNothing() 1514bf952c9SAnna Dabrowska { 1524bf952c9SAnna Dabrowska global $conf; 1534bf952c9SAnna Dabrowska $conf['plugin']['acknowledge']['notification_integration'] = 0; 1544bf952c9SAnna Dabrowska 1554bf952c9SAnna Dabrowska $this->assertEquals([], $this->gather('alice')); 1564bf952c9SAnna Dabrowska } 1574bf952c9SAnna Dabrowska 1584bf952c9SAnna Dabrowska /** 1594bf952c9SAnna Dabrowska * A page blocked by approve is not notified until it is approved. 1604bf952c9SAnna Dabrowska */ 1614bf952c9SAnna Dabrowska public function testApproveBlockedPageNotNotifiedUntilApproved() 1624bf952c9SAnna Dabrowska { 1634bf952c9SAnna Dabrowska $id = 'approved:doc'; 1644bf952c9SAnna Dabrowska saveWikiText($id, 'content', 'test'); 1654bf952c9SAnna Dabrowska $this->approve->handlePageEdit($id); 1664bf952c9SAnna Dabrowska $lastmod = (int) @filemtime(wikiFN($id)); 1674bf952c9SAnna Dabrowska 168*b3fbf6ceSAnna Dabrowska // real saved page: pin the pages row to the exact filemtime directly, since 169*b3fbf6ceSAnna Dabrowska // storePageDate() would compare content and may skip the write 1704bf952c9SAnna Dabrowska $this->db->exec("REPLACE INTO pages(page,lastmod) VALUES (?, ?)", [$id, $lastmod]); 171*b3fbf6ceSAnna Dabrowska $this->helper->setPageAssignees($id, '@staff'); 1724bf952c9SAnna Dabrowska 1734bf952c9SAnna Dabrowska // still a draft -> blocked -> not notified 1744bf952c9SAnna Dabrowska $ids = array_column($this->gather('alice'), 'id'); 1754bf952c9SAnna Dabrowska $this->assertNotContains($id . ':' . $lastmod, $ids); 1764bf952c9SAnna Dabrowska 1774bf952c9SAnna Dabrowska // once approved -> notified 1784bf952c9SAnna Dabrowska $this->approve->setApprovedStatus($id); 1794bf952c9SAnna Dabrowska $ids = array_column($this->gather('alice'), 'id'); 1804bf952c9SAnna Dabrowska $this->assertContains($id . ':' . $lastmod, $ids); 1814bf952c9SAnna Dabrowska } 1824bf952c9SAnna Dabrowska} 183