oldAuthAcl = $AUTH_ACL;
        $this->userinfo = $USERINFO;
        $auth = new AuthPlugin();
        $conf['remote'] = 1;
        $conf['remoteuser'] = '@user';
        $conf['useacl'] = 0;
        $this->remote = new Api();
    }
    public function tearDown(): void
    {
        parent::tearDown();
        global $USERINFO;
        global $AUTH_ACL;
        $USERINFO = $this->userinfo;
        $AUTH_ACL = $this->oldAuthAcl;
    }
    /**
     * Do an assertion that converts to JSON inbetween
     *
     * This lets us compare result objects with arrays
     */
    protected function assertEqualResult($expected, $actual, $msg = '')
    {
        // sort object arrays
        if (is_array($actual) && array_key_exists(0, $actual) && is_object($actual[0])) {
            sort($actual);
            sort($expected);
        }
        $expected = json_decode(json_encode($expected), true);
        $actual = json_decode(json_encode($actual), true);
        $this->assertEquals($expected, $actual, $msg);
    }
    // region info
    // core.getAPIVersion
    public function testGetAPIVersion()
    {
        $this->assertEqualResult(
            ApiCore::API_VERSION,
            $this->remote->call('core.getAPIVersion')
        );
    }
    // core.getWikiVersion
    public function testGetWikiVersion()
    {
        $this->assertEqualResult(
            getVersion(),
            $this->remote->call('core.getWikiVersion')
        );
    }
    // core.getWikiTitle
    public function testGetWikiTitle()
    {
        global $conf;
        $this->assertEqualResult(
            $conf['title'],
            $this->remote->call('core.getWikiTitle')
        );
    }
    // core.getWikiTime
    public function testGetWikiTime()
    {
        $this->assertEqualsWithDelta(
            time(),
            $this->remote->call('core.getWikiTime'),
            1 // allow 1 second difference
        );
    }
    // endregion
    // region user
    // core.login
    public function testLogin()
    {
        $this->markTestIncomplete('Missing test for core.login API Call');
    }
    // core.logoff
    public function testLogoff()
    {
        $this->markTestIncomplete('Missing test for core.logoff API Call');
    }
    // core.whoAmI
    public function testWhoAmI()
    {
        $this->markTestIncomplete('Missing test for core.whoAmI API Call');
    }
    // core.aclCheck -> See also ApiCoreAclCheckTest.php
    public function testAclCheck()
    {
        $id = 'aclpage';
        $this->assertEquals(AUTH_UPLOAD, $this->remote->call('core.aclCheck', ['page' => $id]));
        global $conf;
        global $AUTH_ACL;
        global $USERINFO;
        $conf['useacl'] = 1;
        $_SERVER['REMOTE_USER'] = 'john';
        $USERINFO['grps'] = ['user'];
        $AUTH_ACL = [
            '*                  @ALL           0',
            '*                  @user          2', //edit
        ];
        $this->assertEquals(AUTH_EDIT, $this->remote->call('core.aclCheck', ['page' => $id]));
    }
    // endregion
    // region pages
    // core.listPages
    public function testlistPagesAll()
    {
        // all pages depends on index
        idx_addPage('wiki:syntax');
        idx_addPage('wiki:dokuwiki');
        $file1 = wikiFN('wiki:syntax');
        $file2 = wikiFN('wiki:dokuwiki');
        $expected = [
            [
                'id' => 'wiki:syntax',
                'title' => 'wiki:syntax',
                'permission' => 8,
                'size' => filesize($file1),
                'revision' => filemtime($file1),
                'hash' => md5(trim(io_readFile($file1))),
                'author' => '',
            ],
            [
                'id' => 'wiki:dokuwiki',
                'title' => 'wiki:dokuwiki',
                'permission' => 8,
                'size' => filesize($file2),
                'revision' => filemtime($file2),
                'hash' => md5(trim(io_readFile($file2))),
                'author' => '',
            ]
        ];
        $this->assertEqualResult(
            $expected,
            $this->remote->call(
                'core.listPages',
                [
                    'namespace' => '',
                    'depth' => 0, // 0 for all
                    'hash' => true
                ]
            )
        );
    }
    // core.listPages
    public function testListPagesNamespace()
    {
        $file1 = wikiFN('wiki:syntax');
        $file2 = wikiFN('wiki:dokuwiki');
        // no indexing needed here
        global $conf;
        $conf['useheading'] = 1;
        $expected = [
            [
                'id' => 'wiki:syntax',
                'title' => 'Formatting Syntax',
                'permission' => 8,
                'size' => filesize($file1),
                'revision' => filemtime($file1),
                'hash' => '',
                'author' => '',
            ],
            [
                'id' => 'wiki:dokuwiki',
                'title' => 'DokuWiki',
                'permission' => 8,
                'size' => filesize($file2),
                'revision' => filemtime($file2),
                'hash' => '',
                'author' => '',
            ],
        ];
        $this->assertEqualResult(
            $expected,
            $this->remote->call(
                'core.listPages',
                [
                    'namespace' => 'wiki:',
                    'depth' => 1,
                ]
            )
        );
    }
    // core.searchPages
    public function testSearchPages()
    {
        $id = 'wiki:syntax';
        $file = wikiFN($id);
        idx_addPage($id); //full text search depends on index
        $expected = [
            [
                'id' => $id,
                'score' => 1,
                'revision' => filemtime($file),
                'permission' => 8,
                'size' => filesize($file),
                'snippet' => ' a footnote)) by using double parentheses.
===== Sectioning =====
You can use up to five different levels of',
                'title' => 'wiki:syntax',
                'author' => '',
                'hash' => '',
            ]
        ];
        $this->assertEqualResult(
            $expected,
            $this->remote->call(
                'core.searchPages',
                [
                    'query' => 'Sectioning'
                ]
            )
        );
    }
    //core.getRecentPageChanges
    public function testGetRecentPageChanges()
    {
        $_SERVER['REMOTE_USER'] = 'testuser';
        saveWikiText('pageone', 'test', 'test one');
        $rev1 = filemtime(wikiFN('pageone'));
        saveWikiText('pagetwo', 'test', 'test two');
        $rev2 = filemtime(wikiFN('pagetwo'));
        $expected = [
            [
                'id' => 'pageone',
                'revision' => $rev1,
                'author' => 'testuser',
                'sizechange' => 4,
                'summary' => 'test one',
                'type' => 'C',
                'ip' => clientIP(),
            ],
            [
                'id' => 'pagetwo',
                'revision' => $rev2,
                'author' => 'testuser',
                'sizechange' => 4,
                'summary' => 'test two',
                'type' => 'C',
                'ip' => clientIP(),
            ]
        ];
        $this->assertEqualResult(
            $expected,
            $this->remote->call(
                'core.getRecentPageChanges',
                [
                    'timestamp' => 0 // all recent changes
                ]
            )
        );
    }
    // core.getPage
    public function testGetPage()
    {
        $id = 'pageversion';
        $file = wikiFN($id);
        saveWikiText($id, 'first version', 'first');
        $rev1 = filemtime($file);
        clearstatcache(false, $file);
        $this->waitForTick(true);
        saveWikiText($id, 'second version', 'second');
        $rev2 = filemtime($file);
        $this->assertEqualResult(
            'second version',
            $this->remote->call('core.getPage', ['page' => $id, 'rev' => 0]),
            'no revision given -> current'
        );
        $this->assertEqualResult(
            'first version',
            $this->remote->call('core.getPage', ['page' => $id, 'rev' => $rev1]),
            '1st revision given'
        );
        $this->assertEqualResult(
            'second version',
            $this->remote->call('core.getPage', ['page' => $id, 'rev' => $rev2]),
            '2nd revision given'
        );
        $this->assertEqualResult(
            '',
            $this->remote->call('core.getPage', ['page' => $id, 'rev' => 1234]),
            'Non existing revision given'
        );
        $this->assertEqualResult(
            '',
            $this->remote->call('core.getPage', ['page' => 'foobar', 'rev' => 1234]),
            'Non existing page given'
        );
    }
    //core.getPageHTML
    public function testGetPageHTMLVersion()
    {
        $id = 'htmltest';
        $file = wikiFN($id);
        $content1 = "====Title====\nText";
        $html1 = "\n
Title
\n\n";
        $content2 = "====Foobar====\nText Bamm";
        $html2 = "\nFoobar
\n\n";
        saveWikiText($id, $content1, 'first');
        $rev1 = filemtime($file);
        clearstatcache(false, $file);
        $this->waitForTick(true);
        saveWikiText($id, $content2, 'second');
        $rev2 = filemtime($file);
        $this->assertEqualResult(
            $html2,
            $this->remote->call('core.getPageHTML', ['page' => $id, 'rev' => 0]),
            'no revision given -> current'
        );
        $this->assertEqualResult(
            $html1,
            $this->remote->call('core.getPageHTML', ['page' => $id, 'rev' => $rev1]),
            '1st revision given'
        );
        $this->assertEqualResult(
            $html2,
            $this->remote->call('core.getPageHTML', ['page' => $id, 'rev' => $rev2]),
            '2nd revision given'
        );
        $e = null;
        try {
            $this->remote->call('core.getPageHTML', ['page' => $id, 'rev' => 1234]);
        } catch (RemoteException $e) {
        }
        $this->assertInstanceOf(RemoteException::class, $e);
        $this->assertEquals(121, $e->getCode(), 'Non existing revision given');
        $e = null;
        try {
            $this->remote->call('core.getPageHTML', ['page' => 'foobar', 'rev' => 1234]);
        } catch (RemoteException $e) {
        }
        $this->assertInstanceOf(RemoteException::class, $e);
        $this->assertEquals(121, $e->getCode(), 'Non existing page given');
    }
    //core.getPageInfo
    public function testGetPageInfo()
    {
        $id = 'pageinfo';
        $file = wikiFN($id);
        $_SERVER['REMOTE_USER'] = 'testuser';
        saveWikiText($id, 'first version', 'first');
        $rev1 = filemtime($file);
        clearstatcache(false, $file);
        $this->waitForTick(true);
        saveWikiText($id, 'second version', 'second');
        $rev2 = filemtime($file);
        $expected = [
            'id' => $id,
            'revision' => $rev2,
            'author' => 'testuser',
            'hash' => md5(trim(io_readFile($file))),
            'title' => $id,
            'size' => filesize($file),
            'permission' => 8,
        ];
        $this->assertEqualResult(
            $expected,
            $this->remote->call('core.getPageInfo', ['page' => $id, 'rev' => 0, 'hash' => true, 'author' => true]),
            'no revision given -> current'
        );
        $expected = [
            'id' => $id,
            'revision' => $rev1,
            'author' => '',
            'hash' => '',
            'title' => $id,
            'size' => filesize(wikiFN($id, $rev1)),
            'permission' => 8,
        ];
        $this->assertEqualResult(
            $expected,
            $this->remote->call('core.getPageInfo', ['page' => $id, 'rev' => $rev1]),
            '1st revision given'
        );
        $expected = [
            'id' => $id,
            'revision' => $rev2,
            'author' => '',
            'hash' => '',
            'title' => $id,
            'size' => filesize(wikiFN($id, $rev2)),
            'permission' => 8,
        ];
        $this->assertEqualResult(
            $expected,
            $this->remote->call('core.getPageInfo', ['page' => $id, 'rev' => $rev2]),
            '2nd revision given'
        );
        $e = null;
        try {
            $this->remote->call('core.getPageInfo', ['page' => $id, 'rev' => 1234]);
        } catch (RemoteException $e) {
        }
        $this->assertInstanceOf(RemoteException::class, $e);
        $this->assertEquals(121, $e->getCode(), 'Non existing revision given');
        $e = null;
        try {
            $this->remote->call('core.getPageInfo', ['page' => 'foobar', 'rev' => 1234]);
        } catch (RemoteException $e) {
        }
        $this->assertInstanceOf(RemoteException::class, $e);
        $this->assertEquals(121, $e->getCode(), 'Non existing page given');
    }
    //core.getPageHistory
    public function testGetPageHistory()
    {
        global $conf;
        $id = 'revpage';
        $file = wikiFN($id);
        $rev = [];
        for ($i = 0; $i < 6; $i++) {
            $this->waitForTick();
            saveWikiText($id, "rev$i", "rev$i");
            clearstatcache(false, $file);
            $rev[$i] = filemtime($file);
        }
        $params = ['page' => $id, 'first' => 0];
        $versions = $this->remote->call('core.getPageHistory', $params);
        $versions = json_decode(json_encode($versions), true);
        $this->assertEquals(6, count($versions));
        $this->assertEquals($rev[5], $versions[0]['revision']);
        $this->assertEquals($rev[4], $versions[1]['revision']);
        $this->assertEquals($rev[3], $versions[2]['revision']);
        $this->assertEquals($rev[2], $versions[3]['revision']);
        $this->assertEquals($rev[1], $versions[4]['revision']);
        $this->assertEquals($rev[0], $versions[5]['revision']);
        $params = ['page' => $id, 'first' => 1]; // offset 1
        $versions = $this->remote->call('core.getPageHistory', $params);
        $versions = json_decode(json_encode($versions), true);
        $this->assertEquals(5, count($versions));
        $this->assertEquals($rev[4], $versions[0]['revision']);
        $this->assertEquals($rev[3], $versions[1]['revision']);
        $this->assertEquals($rev[2], $versions[2]['revision']);
        $this->assertEquals($rev[1], $versions[3]['revision']);
        $this->assertEquals($rev[0], $versions[4]['revision']);
        $conf['recent'] = 3; //set number of results per page
        $params = ['page' => $id, 'first' => 0]; // first page
        $versions = $this->remote->call('core.getPageHistory', $params);
        $versions = json_decode(json_encode($versions), true);
        $this->assertEquals(3, count($versions));
        $this->assertEquals($rev[5], $versions[0]['revision']);
        $this->assertEquals($rev[4], $versions[1]['revision']);
        $this->assertEquals($rev[3], $versions[2]['revision']);
        $params = ['page' => $id, 'first' => $conf['recent']]; // second page
        $versions = $this->remote->call('core.getPageHistory', $params);
        $versions = json_decode(json_encode($versions), true);
        $this->assertEquals(3, count($versions));
        $this->assertEquals($rev[2], $versions[0]['revision']);
        $this->assertEquals($rev[1], $versions[1]['revision']);
        $this->assertEquals($rev[0], $versions[2]['revision']);
        $params = ['page' => $id, 'first' => $conf['recent'] * 2]; // third page
        $versions = $this->remote->call('core.getPageHistory', $params);
        $versions = json_decode(json_encode($versions), true);
        $this->assertEquals(0, count($versions));
    }
    //core.getPageLinks
    public function testGetPageLinks()
    {
        $localdoku = [
            'type' => 'local',
            'page' => 'DokuWiki',
            'href' => DOKU_BASE . DOKU_SCRIPT . '?id=DokuWiki'
        ];
        $expected = [
            $localdoku,
            [
                'type' => 'extern',
                'page' => 'http://www.freelists.org',
                'href' => 'http://www.freelists.org'
            ],
            [
                'type' => 'interwiki',
                'page' => 'rfc>1855',
                'href' => 'https://tools.ietf.org/html/rfc1855'
            ],
            [
                'type' => 'extern',
                'page' => 'http://www.catb.org/~esr/faqs/smart-questions.html',
                'href' => 'http://www.catb.org/~esr/faqs/smart-questions.html'
            ],
            $localdoku,
            $localdoku
        ];
        $this->assertEqualResult(
            $expected,
            $this->remote->call('core.getPageLinks', ['page' => 'mailinglist'])
        );
        $this->expectExceptionCode(121);
        $this->remote->call('core.getPageLinks', ['page' => 'foobar']);
    }
    //core.getPageBackLinks
    public function testGetPageBackLinks()
    {
        saveWikiText('linky', '[[wiki:syntax]]', 'test');
        // backlinks need index
        idx_addPage('wiki:syntax');
        idx_addPage('linky');
        $result = $this->remote->call('core.getPageBackLinks', ['page' => 'wiki:syntax']);
        $this->assertTrue(count($result) > 0);
        $this->assertEqualResult(ft_backlinks('wiki:syntax'), $result);
        $this->assertEquals([], $this->remote->call('core.getPageBackLinks', ['page' => 'foobar']));
    }
    //core.lockPages
    public function testLockPages()
    {
        // lock a first set of pages
        $_SERVER['REMOTE_USER'] = 'testuser1';
        $tolock = ['wiki:dokuwiki', 'nonexisting'];
        $this->assertEquals(
            $tolock,
            $this->remote->call('core.lockPages', ['pages' => $tolock]),
            'all pages should lock'
        );
        // now we're someone else
        $_SERVER['REMOTE_USER'] = 'testuser2';
        $tolock = ['wiki:dokuwiki', 'nonexisting', 'wiki:syntax', 'another'];
        $expected = ['wiki:syntax', 'another'];
        $this->assertEquals(
            $expected,
            $this->remote->call('core.lockPages', ['pages' => $tolock]),
            'only half the pages should lock'
        );
    }
    // core.unlockPages
    public function testUnlockPages()
    {
        $_SERVER['REMOTE_USER'] = 'testuser1';
        lock('wiki:dokuwiki');
        lock('nonexisting');
        $_SERVER['REMOTE_USER'] = 'testuser2';
        lock('wiki:syntax');
        lock('another');
        $tounlock = ['wiki:dokuwiki', 'nonexisting', 'wiki:syntax', 'another', 'notlocked'];
        $expected = ['wiki:syntax', 'another'];
        $this->assertEquals(
            $expected,
            $this->remote->call('core.unlockPages', ['pages' => $tounlock])
        );
    }
    //core.savePage
    public function testSavePage()
    {
        $id = 'putpage';
        $content = "====Title====\nText";
        $params = [
            'page' => $id,
            'text' => $content,
            'isminor' => false,
            'summary' => 'Summary of nice text'
        ];
        $this->assertTrue($this->remote->call('core.savePage', $params));
        $this->assertEquals($content, rawWiki($id));
        // remove page
        $params = [
            'page' => $id,
            'text' => '',
        ];
        $this->assertTrue($this->remote->call('core.savePage', $params));
        $this->assertFileNotExists(wikiFN($id));
        // remove non existing page (reusing above params)
        $e = null;
        try {
            $this->remote->call('core.savePage', $params);
        } catch (RemoteException $e) {
        }
        $this->assertInstanceOf(RemoteException::class, $e);
        $this->assertEquals(132, $e->getCode());
    }
    //core.appendPage
    public function testAppendPage()
    {
        $id = 'appendpage';
        $content = 'a test';
        $morecontent = "\nOther text";
        saveWikiText($id, $content, 'local');
        $params = [
            'page' => $id,
            'text' => $morecontent,
        ];
        $this->assertEquals(true, $this->remote->call('core.appendPage', $params));
        $this->assertEquals($content . $morecontent, rawWiki($id));
    }
    // endregion
    // region media
    // core.listMedia
    public function testListMedia()
    {
        $id = 'wiki:dokuwiki-128.png';
        $file = mediaFN($id);
        $content = file_get_contents($file);
        $expected = [
            [
                'id' => $id,
                'size' => filesize($file),
                'revision' => filemtime($file),
                'isimage' => true,
                'hash' => md5($content),
                'permission' => 8,
                'author' => '',
            ]
        ];
        $this->assertEqualResult(
            $expected,
            $this->remote->call(
                'core.listMedia',
                [
                    'namespace' => 'wiki',
                    'pattern' => '/128/',
                    'hash' => true,
                ]
            )
        );
    }
    //core.getRecentMediaChanges
    public function testGetRecentMediaChanges()
    {
        global $conf;
        $_SERVER['REMOTE_USER'] = 'testuser';
        $orig = mediaFN('wiki:dokuwiki-128.png');
        $tmp = $conf['tmpdir'] . 'test.png';
        $target1 = 'test:image1.png';
        $file1 = mediaFN($target1);
        copy($orig, $tmp);
        media_save(['name' => $tmp], $target1, true, AUTH_UPLOAD, 'rename');
        $target2 = 'test:image2.png';
        $file2 = mediaFN($target2);
        copy($orig, $tmp);
        media_save(['name' => $tmp], $target2, true, AUTH_UPLOAD, 'rename');
        $expected = [
            [
                'id' => $target1,
                'revision' => filemtime($file1),
                'author' => 'testuser',
                'ip' => clientIP(),
                'sizechange' => filesize($file1),
                'summary' => 'created',
                'type' => 'C',
            ],
            [
                'id' => $target2,
                'revision' => filemtime($file2),
                'author' => 'testuser',
                'ip' => clientIP(),
                'sizechange' => filesize($file2),
                'summary' => 'created',
                'type' => 'C',
            ]
        ];
        $this->assertEqualResult(
            $expected,
            $this->remote->call(
                'core.getRecentMediaChanges',
                [
                    'timestamp' => 0 // all recent changes
                ]
            )
        );
    }
    //core.getMedia
    public function testGetMedia()
    {
        $id = 'wiki:dokuwiki-128.png';
        $file = mediaFN($id);
        $base64 = base64_encode(file_get_contents($file));
        $this->assertEquals(
            $base64,
            $this->remote->call('core.getMedia', ['media' => $id])
        );
        $e = null;
        try {
            $this->remote->call('core.getMedia', ['media' => $id, 'rev' => 1234]);
        } catch (RemoteException $e) {
        }
        $this->assertInstanceOf(RemoteException::class, $e);
        $this->assertEquals(221, $e->getCode(), 'Non existing revision given');
        $e = null;
        try {
            $this->remote->call('core.getMedia', ['media' => 'foobar.png']);
        } catch (RemoteException $e) {
        }
        $this->assertInstanceOf(RemoteException::class, $e);
        $this->assertEquals(221, $e->getCode(), 'Non existing media id given');
    }
    //core.getMediaInfo
    public function testGetMediaInfo()
    {
        $id = 'wiki:dokuwiki-128.png';
        $file = mediaFN($id);
        $expected = [
            'id' => $id,
            'revision' => filemtime($file),
            'author' => '',
            'hash' => md5(file_get_contents($file)),
            'size' => filesize($file),
            'permission' => 8,
            'isimage' => true,
        ];
        $this->assertEqualResult(
            $expected,
            $this->remote->call('core.getMediaInfo', ['media' => $id, 'hash' => true, 'author' => false])
        );
        $e = null;
        try {
            $this->remote->call('core.getMediaInfo', ['media' => $id, 'rev' => 1234]);
        } catch (RemoteException $e) {
        }
        $this->assertInstanceOf(RemoteException::class, $e);
        $this->assertEquals(221, $e->getCode(), 'Non existing revision given');
        $e = null;
        try {
            $this->remote->call('core.getMediaInfo', ['media' => 'foobar.png']);
        } catch (RemoteException $e) {
        }
        $this->assertInstanceOf(RemoteException::class, $e);
        $this->assertEquals(221, $e->getCode(), 'Non existing media id given');
    }
    //core.saveMedia
    public function testSaveMedia()
    {
        $orig = mediaFN('wiki:dokuwiki-128.png');
        $base64 = base64_encode(file_get_contents($orig));
        $target = 'test:putimage.png';
        $targetfile = mediaFN($target);
        $this->assertTrue($this->remote->call('core.saveMedia', ['media' => $target, 'base64' => $base64]));
        $this->assertFileExists($targetfile);
        $this->assertFileEquals($orig, $targetfile);
    }
    //core.deleteMedia
    public function testDeleteMedia()
    {
        global $conf;
        global $AUTH_ACL;
        global $USERINFO;
        $id = 'wiki:dokuwiki-128.png';
        $file = mediaFN($id);
        // deletion should fail, we only have AUTH_UPLOAD
        $e = null;
        try {
            $this->remote->call('core.deleteMedia', ['media' => $id]);
        } catch (AccessDeniedException $e) {
        }
        $this->assertInstanceOf(AccessDeniedException::class, $e);
        $this->assertEquals(212, $e->getCode(), 'No permission to delete');
        $this->assertFileExists($file);
        // setup new ACLs
        $conf['useacl'] = 1;
        $_SERVER['REMOTE_USER'] = 'john';
        $USERINFO['grps'] = array('user');
        $AUTH_ACL = array(
            '*                  @ALL           0',
            '*                  @user          16',
        );
        // deletion should work now
        $this->assertTrue($this->remote->call('core.deleteMedia', ['media' => $id]));
        $this->assertFileNotExists($file);
        clearstatcache(false, $file);
        // deleting the file again should not work
        $e = null;
        try {
            $this->remote->call('core.deleteMedia', ['media' => $id]);
        } catch (RemoteException $e) {
        }
        $this->assertInstanceOf(RemoteException::class, $e);
        $this->assertEquals(221, $e->getCode(), 'Non existing media id given');
    }
    // endregion
}