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