1*4bf952c9SAnna Dabrowska<?php 2*4bf952c9SAnna Dabrowska 3*4bf952c9SAnna Dabrowskanamespace dokuwiki\plugin\acknowledge\test; 4*4bf952c9SAnna Dabrowska 5*4bf952c9SAnna Dabrowskause DokuWikiTest; 6*4bf952c9SAnna Dabrowskause dokuwiki\Extension\Event; 7*4bf952c9SAnna Dabrowska 8*4bf952c9SAnna Dabrowska/** 9*4bf952c9SAnna Dabrowska * Tests for the notification plugin integration (action/notification.php). 10*4bf952c9SAnna Dabrowska * 11*4bf952c9SAnna Dabrowska * The notification plugin is pull-based: it fires PLUGIN_NOTIFICATION_GATHER per user and 12*4bf952c9SAnna Dabrowska * deduplicates the returned notifications by (plugin, id, user) permanently. These tests drive 13*4bf952c9SAnna Dabrowska * the gather handler directly and assert on the produced notification ids, which encode the 14*4bf952c9SAnna Dabrowska * "$page:$lastmod" semantics that define when a user is (re-)notified. 15*4bf952c9SAnna Dabrowska * 16*4bf952c9SAnna Dabrowska * @group plugin_acknowledge 17*4bf952c9SAnna Dabrowska * @group plugins 18*4bf952c9SAnna Dabrowska */ 19*4bf952c9SAnna Dabrowskaclass NotificationIntegrationTest extends DokuWikiTest 20*4bf952c9SAnna Dabrowska{ 21*4bf952c9SAnna Dabrowska /** @var array */ 22*4bf952c9SAnna Dabrowska protected $pluginsEnabled = ['acknowledge', 'sqlite', 'approve']; 23*4bf952c9SAnna Dabrowska 24*4bf952c9SAnna Dabrowska /** @var \helper_plugin_acknowledge */ 25*4bf952c9SAnna Dabrowska protected $helper; 26*4bf952c9SAnna Dabrowska 27*4bf952c9SAnna Dabrowska /** @var \dokuwiki\plugin\sqlite\SQLiteDB */ 28*4bf952c9SAnna Dabrowska protected $db; 29*4bf952c9SAnna Dabrowska 30*4bf952c9SAnna Dabrowska /** @var \helper_plugin_approve_db */ 31*4bf952c9SAnna Dabrowska protected $approve; 32*4bf952c9SAnna Dabrowska 33*4bf952c9SAnna Dabrowska public static function setUpBeforeClass(): void 34*4bf952c9SAnna Dabrowska { 35*4bf952c9SAnna Dabrowska parent::setUpBeforeClass(); 36*4bf952c9SAnna Dabrowska /** @var \auth_plugin_authplain $auth */ 37*4bf952c9SAnna Dabrowska global $auth; 38*4bf952c9SAnna Dabrowska $auth->createUser('alice', 'none', 'alice', 'alice@example.com', ['staff']); 39*4bf952c9SAnna Dabrowska } 40*4bf952c9SAnna Dabrowska 41*4bf952c9SAnna Dabrowska public function setUp(): void 42*4bf952c9SAnna Dabrowska { 43*4bf952c9SAnna Dabrowska parent::setUp(); 44*4bf952c9SAnna Dabrowska 45*4bf952c9SAnna Dabrowska // setApprovedStatus() records the approving user from $INFO 46*4bf952c9SAnna Dabrowska global $INFO; 47*4bf952c9SAnna Dabrowska $INFO['client'] = 'someapprover'; 48*4bf952c9SAnna Dabrowska 49*4bf952c9SAnna Dabrowska $this->helper = plugin_load('helper', 'acknowledge'); 50*4bf952c9SAnna Dabrowska $this->db = $this->helper->getDB(); 51*4bf952c9SAnna Dabrowska /** @var \helper_plugin_approve_db approve */ 52*4bf952c9SAnna Dabrowska $this->approve = plugin_load('helper', 'approve_db'); 53*4bf952c9SAnna Dabrowska $this->approve->addMaintainer('approved:**', 'someapprover'); 54*4bf952c9SAnna Dabrowska 55*4bf952c9SAnna Dabrowska // due (never acked), current (acked >= lastmod), outdated (acked < lastmod) 56*4bf952c9SAnna Dabrowska $pages = "REPLACE INTO pages(page,lastmod) 57*4bf952c9SAnna Dabrowska VALUES ('wiki:due_new', 1000), 58*4bf952c9SAnna Dabrowska ('wiki:done', 1000), 59*4bf952c9SAnna Dabrowska ('wiki:outdated', 2000)"; 60*4bf952c9SAnna Dabrowska $this->db->exec($pages); 61*4bf952c9SAnna Dabrowska 62*4bf952c9SAnna Dabrowska $assignments = "REPLACE INTO assignments(page,pageassignees) 63*4bf952c9SAnna Dabrowska VALUES ('wiki:due_new', '@staff'), 64*4bf952c9SAnna Dabrowska ('wiki:done', '@staff'), 65*4bf952c9SAnna Dabrowska ('wiki:outdated', '@staff')"; 66*4bf952c9SAnna Dabrowska $this->db->exec($assignments); 67*4bf952c9SAnna Dabrowska 68*4bf952c9SAnna Dabrowska $acks = "REPLACE INTO acks(page,user,ack) 69*4bf952c9SAnna Dabrowska VALUES ('wiki:done', 'alice', 2000), 70*4bf952c9SAnna Dabrowska ('wiki:outdated', 'alice', 1000)"; 71*4bf952c9SAnna Dabrowska $this->db->exec($acks); 72*4bf952c9SAnna Dabrowska } 73*4bf952c9SAnna Dabrowska 74*4bf952c9SAnna Dabrowska /** 75*4bf952c9SAnna Dabrowska * Invoke the real gather handler for a user and return the produced notifications. 76*4bf952c9SAnna Dabrowska * 77*4bf952c9SAnna Dabrowska * @param string $user 78*4bf952c9SAnna Dabrowska * @return array 79*4bf952c9SAnna Dabrowska */ 80*4bf952c9SAnna Dabrowska protected function gather($user) 81*4bf952c9SAnna Dabrowska { 82*4bf952c9SAnna Dabrowska $data = [ 83*4bf952c9SAnna Dabrowska 'plugins' => ['acknowledge'], 84*4bf952c9SAnna Dabrowska 'user' => $user, 85*4bf952c9SAnna Dabrowska 'notifications' => [], 86*4bf952c9SAnna Dabrowska ]; 87*4bf952c9SAnna Dabrowska $event = new Event('PLUGIN_NOTIFICATION_GATHER', $data); 88*4bf952c9SAnna Dabrowska 89*4bf952c9SAnna Dabrowska /** @var \action_plugin_acknowledge_notification $action */ 90*4bf952c9SAnna Dabrowska $action = plugin_load('action', 'acknowledge_notification'); 91*4bf952c9SAnna Dabrowska $action->gatherNotifications($event); 92*4bf952c9SAnna Dabrowska 93*4bf952c9SAnna Dabrowska return $data['notifications']; 94*4bf952c9SAnna Dabrowska } 95*4bf952c9SAnna Dabrowska 96*4bf952c9SAnna Dabrowska /** 97*4bf952c9SAnna Dabrowska * Due and outdated pages are notified; up-to-date pages are not. The id is "$page:$lastmod". 98*4bf952c9SAnna Dabrowska */ 99*4bf952c9SAnna Dabrowska public function testDueAndOutdatedNotifiedCurrentNot() 100*4bf952c9SAnna Dabrowska { 101*4bf952c9SAnna Dabrowska $ids = array_column($this->gather('alice'), 'id'); 102*4bf952c9SAnna Dabrowska sort($ids); 103*4bf952c9SAnna Dabrowska 104*4bf952c9SAnna Dabrowska $this->assertEquals(['wiki:due_new:1000', 'wiki:outdated:2000'], $ids); 105*4bf952c9SAnna Dabrowska } 106*4bf952c9SAnna Dabrowska 107*4bf952c9SAnna Dabrowska /** 108*4bf952c9SAnna Dabrowska * The notification carries the assigned plugin name and a rendered link. 109*4bf952c9SAnna Dabrowska */ 110*4bf952c9SAnna Dabrowska public function testNotificationShape() 111*4bf952c9SAnna Dabrowska { 112*4bf952c9SAnna Dabrowska $notifications = $this->gather('alice'); 113*4bf952c9SAnna Dabrowska $this->assertNotEmpty($notifications); 114*4bf952c9SAnna Dabrowska 115*4bf952c9SAnna Dabrowska $notification = $notifications[0]; 116*4bf952c9SAnna Dabrowska $this->assertEquals('acknowledge', $notification['plugin']); 117*4bf952c9SAnna Dabrowska $this->assertStringContainsString('href', $notification['full']); 118*4bf952c9SAnna Dabrowska $this->assertIsInt($notification['timestamp']); 119*4bf952c9SAnna Dabrowska } 120*4bf952c9SAnna Dabrowska 121*4bf952c9SAnna Dabrowska /** 122*4bf952c9SAnna Dabrowska * A page edit (new lastmod) yields a new id, so the user is re-notified after re-ack falls due. 123*4bf952c9SAnna Dabrowska */ 124*4bf952c9SAnna Dabrowska public function testPageChangeProducesNewId() 125*4bf952c9SAnna Dabrowska { 126*4bf952c9SAnna Dabrowska $before = array_column($this->gather('alice'), 'id'); 127*4bf952c9SAnna Dabrowska $this->assertContains('wiki:due_new:1000', $before); 128*4bf952c9SAnna Dabrowska 129*4bf952c9SAnna Dabrowska // simulate a page edit bumping the stored modification date 130*4bf952c9SAnna Dabrowska $this->db->exec("UPDATE pages SET lastmod = 1500 WHERE page = 'wiki:due_new'"); 131*4bf952c9SAnna Dabrowska 132*4bf952c9SAnna Dabrowska $after = array_column($this->gather('alice'), 'id'); 133*4bf952c9SAnna Dabrowska $this->assertContains('wiki:due_new:1500', $after); 134*4bf952c9SAnna Dabrowska $this->assertNotContains('wiki:due_new:1000', $after); 135*4bf952c9SAnna Dabrowska } 136*4bf952c9SAnna Dabrowska 137*4bf952c9SAnna Dabrowska /** 138*4bf952c9SAnna Dabrowska * Changing assignment rules (without a page edit) keeps the same id, so dedup prevents re-notify. 139*4bf952c9SAnna Dabrowska */ 140*4bf952c9SAnna Dabrowska public function testAssignmentChurnKeepsSameId() 141*4bf952c9SAnna Dabrowska { 142*4bf952c9SAnna Dabrowska $before = array_column($this->gather('alice'), 'id'); 143*4bf952c9SAnna Dabrowska $this->assertContains('wiki:due_new:1000', $before); 144*4bf952c9SAnna Dabrowska 145*4bf952c9SAnna Dabrowska // rule churn: widen the assignees but leave lastmod untouched 146*4bf952c9SAnna Dabrowska $this->db->exec( 147*4bf952c9SAnna Dabrowska "REPLACE INTO assignments(page,pageassignees) VALUES ('wiki:due_new', '@staff,@other')" 148*4bf952c9SAnna Dabrowska ); 149*4bf952c9SAnna Dabrowska 150*4bf952c9SAnna Dabrowska $after = array_column($this->gather('alice'), 'id'); 151*4bf952c9SAnna Dabrowska $this->assertContains('wiki:due_new:1000', $after); 152*4bf952c9SAnna Dabrowska } 153*4bf952c9SAnna Dabrowska 154*4bf952c9SAnna Dabrowska /** 155*4bf952c9SAnna Dabrowska * With the integration disabled, the gather handler produces nothing. 156*4bf952c9SAnna Dabrowska */ 157*4bf952c9SAnna Dabrowska public function testDisabledIntegrationProducesNothing() 158*4bf952c9SAnna Dabrowska { 159*4bf952c9SAnna Dabrowska global $conf; 160*4bf952c9SAnna Dabrowska $conf['plugin']['acknowledge']['notification_integration'] = 0; 161*4bf952c9SAnna Dabrowska 162*4bf952c9SAnna Dabrowska $this->assertEquals([], $this->gather('alice')); 163*4bf952c9SAnna Dabrowska } 164*4bf952c9SAnna Dabrowska 165*4bf952c9SAnna Dabrowska /** 166*4bf952c9SAnna Dabrowska * A page blocked by approve is not notified until it is approved. 167*4bf952c9SAnna Dabrowska */ 168*4bf952c9SAnna Dabrowska public function testApproveBlockedPageNotNotifiedUntilApproved() 169*4bf952c9SAnna Dabrowska { 170*4bf952c9SAnna Dabrowska $id = 'approved:doc'; 171*4bf952c9SAnna Dabrowska saveWikiText($id, 'content', 'test'); 172*4bf952c9SAnna Dabrowska $this->approve->handlePageEdit($id); 173*4bf952c9SAnna Dabrowska $lastmod = (int) @filemtime(wikiFN($id)); 174*4bf952c9SAnna Dabrowska 175*4bf952c9SAnna Dabrowska $this->db->exec("REPLACE INTO pages(page,lastmod) VALUES (?, ?)", [$id, $lastmod]); 176*4bf952c9SAnna Dabrowska $this->db->exec("REPLACE INTO assignments(page,pageassignees) VALUES (?, '@staff')", [$id]); 177*4bf952c9SAnna Dabrowska 178*4bf952c9SAnna Dabrowska // still a draft -> blocked -> not notified 179*4bf952c9SAnna Dabrowska $ids = array_column($this->gather('alice'), 'id'); 180*4bf952c9SAnna Dabrowska $this->assertNotContains($id . ':' . $lastmod, $ids); 181*4bf952c9SAnna Dabrowska 182*4bf952c9SAnna Dabrowska // once approved -> notified 183*4bf952c9SAnna Dabrowska $this->approve->setApprovedStatus($id); 184*4bf952c9SAnna Dabrowska $ids = array_column($this->gather('alice'), 'id'); 185*4bf952c9SAnna Dabrowska $this->assertContains($id . ':' . $lastmod, $ids); 186*4bf952c9SAnna Dabrowska } 187*4bf952c9SAnna Dabrowska} 188