xref: /dokuwiki/_test/tests/ChangeLog/PageChangeLogTest.php (revision eab6268cca3310499bb33a53846d6e00bebf8f3a)
12bde879aSAndreas Gohr<?php
22bde879aSAndreas Gohr
32bde879aSAndreas Gohrnamespace dokuwiki\test\ChangeLog;
42bde879aSAndreas Gohr
52bde879aSAndreas Gohruse dokuwiki\ChangeLog\PageChangeLog;
62bde879aSAndreas Gohr
72bde879aSAndreas Gohr/**
82bde879aSAndreas Gohr * Tests for dokuwiki\ChangeLog\PageChangeLog.
92bde879aSAndreas Gohr */
102bde879aSAndreas Gohrclass PageChangeLogTest extends \DokuWikiTest
112bde879aSAndreas Gohr{
122bde879aSAndreas Gohr    /**
132bde879aSAndreas Gohr     * A page deleted through DokuWiki is recorded as its own revision, newer than the
142bde879aSAndreas Gohr     * last revision that still had content. getRelativeRevision() must walk back from
152bde879aSAndreas Gohr     * that deletion entry to the last content revision (issue #4635).
162bde879aSAndreas Gohr     */
172bde879aSAndreas Gohr    public function testRevisionBeforeNormalDeletion()
182bde879aSAndreas Gohr    {
192bde879aSAndreas Gohr        $page = 'changelog_deleted';
202bde879aSAndreas Gohr        saveWikiText($page, 'first content', 'create', false);
212bde879aSAndreas Gohr        $this->waitForTick(true);
222bde879aSAndreas Gohr        saveWikiText($page, 'second content longer', 'edit', false);
232bde879aSAndreas Gohr        $this->waitForTick(true);
242bde879aSAndreas Gohr
252bde879aSAndreas Gohr        $editRev = (new PageChangeLog($page))->currentRevision();
262bde879aSAndreas Gohr
272bde879aSAndreas Gohr        saveWikiText($page, '', 'delete', false);
282bde879aSAndreas Gohr        clearstatcache();
292bde879aSAndreas Gohr
302bde879aSAndreas Gohr        $changelog = new PageChangeLog($page);
312bde879aSAndreas Gohr        $delRev = $changelog->currentRevision();
322bde879aSAndreas Gohr
332bde879aSAndreas Gohr        $this->assertNotEquals($editRev, $delRev, 'deletion should get its own revision');
342bde879aSAndreas Gohr        $this->assertEquals(
352bde879aSAndreas Gohr            DOKU_CHANGE_TYPE_DELETE,
362bde879aSAndreas Gohr            $changelog->getRevisionInfo($delRev)['type'],
372bde879aSAndreas Gohr            'current revision should be the deletion'
382bde879aSAndreas Gohr        );
392bde879aSAndreas Gohr        $this->assertEquals(
402bde879aSAndreas Gohr            $editRev,
412bde879aSAndreas Gohr            $changelog->getRelativeRevision($delRev, -1),
422bde879aSAndreas Gohr            'the revision before the deletion should be the last edit'
432bde879aSAndreas Gohr        );
442bde879aSAndreas Gohr    }
452bde879aSAndreas Gohr
462bde879aSAndreas Gohr    /**
472bde879aSAndreas Gohr     * An external deletion is detected and persisted on first read as its own revision
482bde879aSAndreas Gohr     * with an unknown exact date, newer than the last content revision.
492bde879aSAndreas Gohr     * getRelativeRevision() must walk back from it to that last content revision
502bde879aSAndreas Gohr     * (issue #4635).
512bde879aSAndreas Gohr     */
522bde879aSAndreas Gohr    public function testRevisionBeforeExternalDeletion()
532bde879aSAndreas Gohr    {
542bde879aSAndreas Gohr        $page = 'changelog_extdeleted';
552bde879aSAndreas Gohr        saveWikiText($page, 'first content', 'create', false);
562bde879aSAndreas Gohr        $this->waitForTick(true);
572bde879aSAndreas Gohr        saveWikiText($page, 'second content longer', 'edit', false);
582bde879aSAndreas Gohr        $this->waitForTick(true);
592bde879aSAndreas Gohr
602bde879aSAndreas Gohr        $editRev = (new PageChangeLog($page))->currentRevision();
612bde879aSAndreas Gohr
622bde879aSAndreas Gohr        // delete the page file externally, bypassing DokuWiki
632bde879aSAndreas Gohr        unlink(wikiFN($page));
642bde879aSAndreas Gohr        clearstatcache();
652bde879aSAndreas Gohr
662bde879aSAndreas Gohr        // first read detects and persists the external deletion
672bde879aSAndreas Gohr        $changelog = new PageChangeLog($page);
682bde879aSAndreas Gohr        $delRev = $changelog->currentRevision();
692bde879aSAndreas Gohr        $delInfo = $changelog->getRevisionInfo($delRev);
702bde879aSAndreas Gohr
712bde879aSAndreas Gohr        $this->assertNotEquals($editRev, $delRev, 'external deletion should get its own revision');
722bde879aSAndreas Gohr        $this->assertEquals(DOKU_CHANGE_TYPE_DELETE, $delInfo['type'], 'current revision should be the deletion');
732bde879aSAndreas Gohr        $this->assertFalse($delInfo['timestamp'], 'external deletion has an unknown exact date');
742bde879aSAndreas Gohr        $this->assertEquals(
752bde879aSAndreas Gohr            $editRev,
762bde879aSAndreas Gohr            $changelog->getRelativeRevision($delRev, -1),
772bde879aSAndreas Gohr            'the revision before the external deletion should be the last edit'
782bde879aSAndreas Gohr        );
792bde879aSAndreas Gohr    }
800a245329SAndreas Gohr
810a245329SAndreas Gohr    /**
820a245329SAndreas Gohr     * A current revision's file can have its modification time bumped without any content
830a245329SAndreas Gohr     * change (a backup restore, a git checkout, ...). That must not be recorded as an
840a245329SAndreas Gohr     * external edit: the content is compared against the last revision and, when identical,
850a245329SAndreas Gohr     * the file mtime is reset to the recorded revision date instead (issue #4634).
860a245329SAndreas Gohr     */
870a245329SAndreas Gohr    public function testTouchedFileWithUnchangedContentIsNotExternalEdit()
880a245329SAndreas Gohr    {
890a245329SAndreas Gohr        $page = 'changelog_touched';
900a245329SAndreas Gohr        saveWikiText($page, 'first content', 'create', false);
910a245329SAndreas Gohr
920a245329SAndreas Gohr        $changelog = new PageChangeLog($page);
930a245329SAndreas Gohr        $lastRev = $changelog->currentRevision();
940a245329SAndreas Gohr
950a245329SAndreas Gohr        // bump the file mtime forward without changing the content
960a245329SAndreas Gohr        touch(wikiFN($page), $lastRev + 1000);
970a245329SAndreas Gohr        clearstatcache();
980a245329SAndreas Gohr
990a245329SAndreas Gohr        $changelog = new PageChangeLog($page);
1000a245329SAndreas Gohr        $currentRev = $changelog->currentRevision();
1010a245329SAndreas Gohr        $currentInfo = $changelog->getRevisionInfo($currentRev);
1020a245329SAndreas Gohr
1030a245329SAndreas Gohr        $this->assertEquals($lastRev, $currentRev, 'unchanged content must not create an external revision');
1040a245329SAndreas Gohr        $this->assertArrayNotHasKey('timestamp', $currentInfo, 'should not be a synthesized external edit');
1050a245329SAndreas Gohr        $this->assertCount(1, $changelog->getRevisions(-1, 200), 'no external edit entry should be added');
1060a245329SAndreas Gohr
1070a245329SAndreas Gohr        clearstatcache();
1080a245329SAndreas Gohr        $this->assertEquals($lastRev, filemtime(wikiFN($page)), 'file mtime should be reset to the changelog date');
1090a245329SAndreas Gohr    }
110*eab6268cSAndreas Gohr
111*eab6268cSAndreas Gohr    /**
112*eab6268cSAndreas Gohr     * A detected external edit whose date predates the most recent change already recorded
113*eab6268cSAndreas Gohr     * in the global changelog must stay out of the recent-changes feed (or it would appear
114*eab6268cSAndreas Gohr     * above newer entries with an old date), but is still recorded in the page's own
115*eab6268cSAndreas Gohr     * changelog (issue #4634).
116*eab6268cSAndreas Gohr     */
117*eab6268cSAndreas Gohr    public function testOutOfOrderExternalEditKeptOutOfGlobalChangelog()
118*eab6268cSAndreas Gohr    {
119*eab6268cSAndreas Gohr        global $conf;
120*eab6268cSAndreas Gohr        $page = 'changelog_outoforder';
121*eab6268cSAndreas Gohr        saveWikiText($page, 'first content', 'create', false);
122*eab6268cSAndreas Gohr
123*eab6268cSAndreas Gohr        $changelog = new PageChangeLog($page);
124*eab6268cSAndreas Gohr        $createRev = $changelog->currentRevision();
125*eab6268cSAndreas Gohr
126*eab6268cSAndreas Gohr        // external edit with different content, dated after the create but well before the
127*eab6268cSAndreas Gohr        // global changelog's last-modified time
128*eab6268cSAndreas Gohr        $globalFile = $conf['changelog'];
129*eab6268cSAndreas Gohr        $extRev = $createRev + 10;
130*eab6268cSAndreas Gohr        file_put_contents(wikiFN($page), 'externally edited content');
131*eab6268cSAndreas Gohr        touch(wikiFN($page), $extRev);
132*eab6268cSAndreas Gohr        touch($globalFile, $createRev + 100000);
133*eab6268cSAndreas Gohr        clearstatcache();
134*eab6268cSAndreas Gohr
135*eab6268cSAndreas Gohr        $changelog = new PageChangeLog($page);
136*eab6268cSAndreas Gohr        $detectedRev = $changelog->currentRevision();
137*eab6268cSAndreas Gohr        $detectedInfo = $changelog->getRevisionInfo($detectedRev);
138*eab6268cSAndreas Gohr
139*eab6268cSAndreas Gohr        // detected and recorded in the page's own changelog: the create plus the external edit
140*eab6268cSAndreas Gohr        $this->assertEquals($extRev, $detectedRev, 'external edit should be detected at the file mtime');
141*eab6268cSAndreas Gohr        $this->assertEquals(DOKU_CHANGE_TYPE_EDIT, $detectedInfo['type'], 'should be an external edit');
142*eab6268cSAndreas Gohr        $this->assertEquals(
143*eab6268cSAndreas Gohr            [$extRev, $createRev],
144*eab6268cSAndreas Gohr            $changelog->getRevisions(-1, 200),
145*eab6268cSAndreas Gohr            'page changelog should hold the create and the external edit'
146*eab6268cSAndreas Gohr        );
147*eab6268cSAndreas Gohr
148*eab6268cSAndreas Gohr        // ...but kept out of the global recent-changes feed
149*eab6268cSAndreas Gohr        $this->assertStringNotContainsString(
150*eab6268cSAndreas Gohr            "$extRev\t",
151*eab6268cSAndreas Gohr            file_get_contents($globalFile),
152*eab6268cSAndreas Gohr            'out-of-order external edit must not be appended to the global changelog'
153*eab6268cSAndreas Gohr        );
154*eab6268cSAndreas Gohr    }
155*eab6268cSAndreas Gohr
156*eab6268cSAndreas Gohr    /**
157*eab6268cSAndreas Gohr     * A genuinely current external edit (dated at or after the global changelog's last
158*eab6268cSAndreas Gohr     * recorded change) must still reach the recent-changes feed (issue #4634).
159*eab6268cSAndreas Gohr     */
160*eab6268cSAndreas Gohr    public function testCurrentExternalEditReachesGlobalChangelog()
161*eab6268cSAndreas Gohr    {
162*eab6268cSAndreas Gohr        global $conf;
163*eab6268cSAndreas Gohr        $page = 'changelog_freshext';
164*eab6268cSAndreas Gohr        saveWikiText($page, 'first content', 'create', false);
165*eab6268cSAndreas Gohr
166*eab6268cSAndreas Gohr        $changelog = new PageChangeLog($page);
167*eab6268cSAndreas Gohr        $createRev = $changelog->currentRevision();
168*eab6268cSAndreas Gohr
169*eab6268cSAndreas Gohr        // external edit dated after the create, so it is newer than the feed's most recent
170*eab6268cSAndreas Gohr        // change (the create just written there) and should be appended
171*eab6268cSAndreas Gohr        $extRev = $createRev + 100;
172*eab6268cSAndreas Gohr        file_put_contents(wikiFN($page), 'externally edited content');
173*eab6268cSAndreas Gohr        touch(wikiFN($page), $extRev);
174*eab6268cSAndreas Gohr        clearstatcache();
175*eab6268cSAndreas Gohr
176*eab6268cSAndreas Gohr        $changelog = new PageChangeLog($page);
177*eab6268cSAndreas Gohr        $changelog->currentRevision();
178*eab6268cSAndreas Gohr
179*eab6268cSAndreas Gohr        $this->assertStringContainsString(
180*eab6268cSAndreas Gohr            "$extRev\t",
181*eab6268cSAndreas Gohr            file_get_contents($conf['changelog']),
182*eab6268cSAndreas Gohr            'a current external edit should be appended to the global changelog'
183*eab6268cSAndreas Gohr        );
184*eab6268cSAndreas Gohr    }
1852bde879aSAndreas Gohr}
186