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