xref: /plugin/annotations/_test/HelperTest.php (revision da56206cc13612db0df36be97c0f01d8f3c5e9f4)
1*da56206cStracker-user<?php
2*da56206cStracker-user
3*da56206cStracker-usernamespace dokuwiki\plugin\annotations\test;
4*da56206cStracker-user
5*da56206cStracker-useruse DokuWikiTest;
6*da56206cStracker-user
7*da56206cStracker-user/**
8*da56206cStracker-user * Storage, CRUD, permission and orphan-detection tests for the annotations
9*da56206cStracker-user * helper. The helper is pure logic, so most of this needs no HTTP request.
10*da56206cStracker-user *
11*da56206cStracker-user * @group plugin_annotations
12*da56206cStracker-user * @group plugins
13*da56206cStracker-user */
14*da56206cStracker-userclass HelperTest extends DokuWikiTest
15*da56206cStracker-user{
16*da56206cStracker-user    protected $pluginsEnabled = ['annotations'];
17*da56206cStracker-user
18*da56206cStracker-user    /**
19*da56206cStracker-user     * @return \helper_plugin_annotations
20*da56206cStracker-user     */
21*da56206cStracker-user    protected function helper()
22*da56206cStracker-user    {
23*da56206cStracker-user        return new \helper_plugin_annotations();
24*da56206cStracker-user    }
25*da56206cStracker-user
26*da56206cStracker-user    // -----------------------------------------------------------------
27*da56206cStracker-user    //  Permission rules (pure functions)
28*da56206cStracker-user    // -----------------------------------------------------------------
29*da56206cStracker-user
30*da56206cStracker-user    public function testCanAnnotateRequiresLoginAndRead(): void
31*da56206cStracker-user    {
32*da56206cStracker-user        $h = $this->helper();
33*da56206cStracker-user        $this->assertFalse($h->canAnnotate('', AUTH_READ), 'anonymous may not annotate');
34*da56206cStracker-user        $this->assertFalse($h->canAnnotate('alice', AUTH_NONE), 'no read access → no annotate');
35*da56206cStracker-user        $this->assertTrue($h->canAnnotate('alice', AUTH_READ), 'logged in + read → annotate');
36*da56206cStracker-user        $this->assertTrue($h->canAnnotate('alice', AUTH_EDIT), 'edit access implies read');
37*da56206cStracker-user    }
38*da56206cStracker-user
39*da56206cStracker-user    public function testCanEditAnnotationAuthorOrAdmin(): void
40*da56206cStracker-user    {
41*da56206cStracker-user        $h = $this->helper();
42*da56206cStracker-user        $ann = ['author' => 'alice'];
43*da56206cStracker-user        $this->assertTrue($h->canEditAnnotation($ann, 'alice', false), 'author may edit');
44*da56206cStracker-user        $this->assertFalse($h->canEditAnnotation($ann, 'bob', false), 'non-author may not edit');
45*da56206cStracker-user        $this->assertTrue($h->canEditAnnotation($ann, 'bob', true), 'admin may edit anyone');
46*da56206cStracker-user        $this->assertFalse($h->canEditAnnotation($ann, '', true), 'anonymous never edits');
47*da56206cStracker-user    }
48*da56206cStracker-user
49*da56206cStracker-user    public function testCanEditReplyAuthorOrAdmin(): void
50*da56206cStracker-user    {
51*da56206cStracker-user        $h = $this->helper();
52*da56206cStracker-user        $reply = ['author' => 'alice'];
53*da56206cStracker-user        $this->assertTrue($h->canEditReply($reply, 'alice', false));
54*da56206cStracker-user        $this->assertFalse($h->canEditReply($reply, 'bob', false));
55*da56206cStracker-user        $this->assertTrue($h->canEditReply($reply, 'bob', true));
56*da56206cStracker-user    }
57*da56206cStracker-user
58*da56206cStracker-user    public function testCanClearAdminOnly(): void
59*da56206cStracker-user    {
60*da56206cStracker-user        $h = $this->helper();
61*da56206cStracker-user        $this->assertTrue($h->canClear(true));
62*da56206cStracker-user        $this->assertFalse($h->canClear(false));
63*da56206cStracker-user    }
64*da56206cStracker-user
65*da56206cStracker-user    // -----------------------------------------------------------------
66*da56206cStracker-user    //  Annotation CRUD
67*da56206cStracker-user    // -----------------------------------------------------------------
68*da56206cStracker-user
69*da56206cStracker-user    public function testCreateGetAndStats(): void
70*da56206cStracker-user    {
71*da56206cStracker-user        $h  = $this->helper();
72*da56206cStracker-user        $id = 'anntest:crud';
73*da56206cStracker-user
74*da56206cStracker-user        $ann = $h->createAnnotation($id, ['exact' => 'hello world'], 'alice', 'A comment');
75*da56206cStracker-user        $this->assertIsArray($ann);
76*da56206cStracker-user        $this->assertNotEmpty($ann['id']);
77*da56206cStracker-user        $this->assertEquals('open', $ann['status']);
78*da56206cStracker-user        $this->assertEquals('alice', $ann['author']);
79*da56206cStracker-user
80*da56206cStracker-user        $this->assertCount(1, $h->getAnnotations($id));
81*da56206cStracker-user        $this->assertEquals($ann['id'], $h->getAnnotation($id, $ann['id'])['id']);
82*da56206cStracker-user        $this->assertEquals(['total' => 1, 'open' => 1, 'resolved' => 0], $h->getStats($id));
83*da56206cStracker-user    }
84*da56206cStracker-user
85*da56206cStracker-user    public function testCreateRejectsEmptyBodyAnchorOrAuthor(): void
86*da56206cStracker-user    {
87*da56206cStracker-user        $h  = $this->helper();
88*da56206cStracker-user        $id = 'anntest:reject';
89*da56206cStracker-user
90*da56206cStracker-user        $this->assertFalse($h->createAnnotation($id, ['exact' => 'x'], 'alice', '   '), 'empty body');
91*da56206cStracker-user        $this->assertFalse($h->createAnnotation($id, ['exact' => ''], 'alice', 'body'), 'empty exact');
92*da56206cStracker-user        $this->assertFalse($h->createAnnotation($id, ['exact' => 'x'], '', 'body'), 'empty author');
93*da56206cStracker-user        $this->assertSame([], $h->getAnnotations($id), 'nothing was stored');
94*da56206cStracker-user    }
95*da56206cStracker-user
96*da56206cStracker-user    public function testBodyAndQuoteAreLengthCapped(): void
97*da56206cStracker-user    {
98*da56206cStracker-user        $h  = $this->helper();
99*da56206cStracker-user        $id = 'anntest:caps';
100*da56206cStracker-user
101*da56206cStracker-user        $ann = $h->createAnnotation($id, ['exact' => str_repeat('q', 2000)], 'alice', str_repeat('x', 20000));
102*da56206cStracker-user        $this->assertIsArray($ann);
103*da56206cStracker-user        $this->assertEquals(10000, mb_strlen($ann['body']), 'body capped at MAX_BODY');
104*da56206cStracker-user        $this->assertEquals(1000, mb_strlen($ann['anchor']['exact']), 'quote capped at MAX_QUOTE');
105*da56206cStracker-user    }
106*da56206cStracker-user
107*da56206cStracker-user    public function testWhitespaceNormalisedInAnchor(): void
108*da56206cStracker-user    {
109*da56206cStracker-user        $h  = $this->helper();
110*da56206cStracker-user        $id = 'anntest:ws';
111*da56206cStracker-user        $ann = $h->createAnnotation($id, ['exact' => "  foo\n\t  bar  "], 'alice', 'b');
112*da56206cStracker-user        $this->assertEquals('foo bar', $ann['anchor']['exact']);
113*da56206cStracker-user    }
114*da56206cStracker-user
115*da56206cStracker-user    public function testUpdateAndDeleteAnnotation(): void
116*da56206cStracker-user    {
117*da56206cStracker-user        $h  = $this->helper();
118*da56206cStracker-user        $id = 'anntest:upd';
119*da56206cStracker-user        $ann = $h->createAnnotation($id, ['exact' => 'foo'], 'alice', 'first');
120*da56206cStracker-user
121*da56206cStracker-user        $this->assertTrue($h->updateAnnotationBody($id, $ann['id'], 'second'));
122*da56206cStracker-user        $this->assertEquals('second', $h->getAnnotation($id, $ann['id'])['body']);
123*da56206cStracker-user        $this->assertFalse($h->updateAnnotationBody($id, 'nope', 'x'), 'missing id → false');
124*da56206cStracker-user        $this->assertFalse($h->updateAnnotationBody($id, $ann['id'], '   '), 'empty body → false');
125*da56206cStracker-user
126*da56206cStracker-user        $this->assertTrue($h->deleteAnnotation($id, $ann['id']));
127*da56206cStracker-user        $this->assertNull($h->getAnnotation($id, $ann['id']));
128*da56206cStracker-user        $this->assertFalse($h->deleteAnnotation($id, $ann['id']), 'already gone → false');
129*da56206cStracker-user    }
130*da56206cStracker-user
131*da56206cStracker-user    public function testStatusFlow(): void
132*da56206cStracker-user    {
133*da56206cStracker-user        $h  = $this->helper();
134*da56206cStracker-user        $id = 'anntest:status';
135*da56206cStracker-user        $ann = $h->createAnnotation($id, ['exact' => 'foo'], 'alice', 'b');
136*da56206cStracker-user
137*da56206cStracker-user        $this->assertTrue($h->setStatus($id, $ann['id'], 'resolved', 'bob'));
138*da56206cStracker-user        $resolved = $h->getAnnotation($id, $ann['id']);
139*da56206cStracker-user        $this->assertEquals('resolved', $resolved['status']);
140*da56206cStracker-user        $this->assertEquals('bob', $resolved['resolved_by']);
141*da56206cStracker-user        $this->assertGreaterThan(0, $resolved['resolved_at']);
142*da56206cStracker-user
143*da56206cStracker-user        $this->assertTrue($h->setStatus($id, $ann['id'], 'open', 'bob'));
144*da56206cStracker-user        $reopened = $h->getAnnotation($id, $ann['id']);
145*da56206cStracker-user        $this->assertEquals('open', $reopened['status']);
146*da56206cStracker-user        $this->assertEquals('', $reopened['resolved_by']);
147*da56206cStracker-user
148*da56206cStracker-user        $this->assertFalse($h->setStatus($id, $ann['id'], 'bogus', 'bob'), 'invalid status → false');
149*da56206cStracker-user    }
150*da56206cStracker-user
151*da56206cStracker-user    // -----------------------------------------------------------------
152*da56206cStracker-user    //  Reply CRUD
153*da56206cStracker-user    // -----------------------------------------------------------------
154*da56206cStracker-user
155*da56206cStracker-user    public function testReplyCrud(): void
156*da56206cStracker-user    {
157*da56206cStracker-user        $h  = $this->helper();
158*da56206cStracker-user        $id = 'anntest:reply';
159*da56206cStracker-user        $ann = $h->createAnnotation($id, ['exact' => 'foo'], 'alice', 'b');
160*da56206cStracker-user
161*da56206cStracker-user        $reply = $h->addReply($id, $ann['id'], 'bob', 'a reply');
162*da56206cStracker-user        $this->assertIsArray($reply);
163*da56206cStracker-user        $this->assertNotEmpty($reply['id']);
164*da56206cStracker-user        $this->assertCount(1, $h->getAnnotation($id, $ann['id'])['replies']);
165*da56206cStracker-user
166*da56206cStracker-user        $this->assertTrue($h->updateReply($id, $ann['id'], $reply['id'], 'edited reply'));
167*da56206cStracker-user        $this->assertEquals('edited reply', $h->getAnnotation($id, $ann['id'])['replies'][0]['body']);
168*da56206cStracker-user
169*da56206cStracker-user        $this->assertTrue($h->deleteReply($id, $ann['id'], $reply['id']));
170*da56206cStracker-user        $this->assertCount(0, $h->getAnnotation($id, $ann['id'])['replies']);
171*da56206cStracker-user
172*da56206cStracker-user        $this->assertFalse($h->addReply($id, 'missing-ann', 'bob', 'x'), 'reply to missing annotation → false');
173*da56206cStracker-user    }
174*da56206cStracker-user
175*da56206cStracker-user    // -----------------------------------------------------------------
176*da56206cStracker-user    //  Bulk maintenance
177*da56206cStracker-user    // -----------------------------------------------------------------
178*da56206cStracker-user
179*da56206cStracker-user    public function testClearResolved(): void
180*da56206cStracker-user    {
181*da56206cStracker-user        $h  = $this->helper();
182*da56206cStracker-user        $id = 'anntest:clearres';
183*da56206cStracker-user        $keep = $h->createAnnotation($id, ['exact' => 'one'], 'alice', 'b1');
184*da56206cStracker-user        $drop = $h->createAnnotation($id, ['exact' => 'two'], 'alice', 'b2');
185*da56206cStracker-user        $h->setStatus($id, $drop['id'], 'resolved', 'alice');
186*da56206cStracker-user
187*da56206cStracker-user        $this->assertEquals(1, $h->clearResolved($id));
188*da56206cStracker-user        $remaining = $h->getAnnotations($id);
189*da56206cStracker-user        $this->assertCount(1, $remaining);
190*da56206cStracker-user        $this->assertEquals($keep['id'], $remaining[0]['id']);
191*da56206cStracker-user    }
192*da56206cStracker-user
193*da56206cStracker-user    // -----------------------------------------------------------------
194*da56206cStracker-user    //  Orphan detection against a rendered page
195*da56206cStracker-user    // -----------------------------------------------------------------
196*da56206cStracker-user
197*da56206cStracker-user    public function testFindAndClearOrphanedAgainstRenderedPage(): void
198*da56206cStracker-user    {
199*da56206cStracker-user        $id = 'anntest:orphan';
200*da56206cStracker-user        saveWikiText($id, 'Hello world, this is a wiki page about cats.', 'setup');
201*da56206cStracker-user
202*da56206cStracker-user        $h = $this->helper();
203*da56206cStracker-user        $present = $h->createAnnotation($id, ['exact' => 'wiki page about cats'], 'alice', 'present');
204*da56206cStracker-user        $gone    = $h->createAnnotation($id, ['exact' => 'text that is not here'], 'alice', 'gone');
205*da56206cStracker-user
206*da56206cStracker-user        $orphanIds = array_map(static function ($a) {
207*da56206cStracker-user            return $a['id'];
208*da56206cStracker-user        }, $h->findOrphaned($id));
209*da56206cStracker-user
210*da56206cStracker-user        $this->assertContains($gone['id'], $orphanIds, 'a missing quote is orphaned');
211*da56206cStracker-user        $this->assertNotContains($present['id'], $orphanIds, 'a present quote is not orphaned');
212*da56206cStracker-user
213*da56206cStracker-user        $this->assertEquals(1, $h->clearOrphaned($id), 'only the orphan is cleared');
214*da56206cStracker-user        $remaining = $h->getAnnotations($id);
215*da56206cStracker-user        $this->assertCount(1, $remaining);
216*da56206cStracker-user        $this->assertEquals($present['id'], $remaining[0]['id']);
217*da56206cStracker-user    }
218*da56206cStracker-user}
219