xref: /plugin/acknowledge/_test/NotificationIntegrationTest.php (revision b3fbf6cebaba3dfb515c3805fdd568dfa76ebe93)
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