xref: /plugin/statistics/_test/LoggerTest.php (revision de1daf8ca85e9cc0cb19b754da18beae1ef5a2b0)
1<?php
2
3namespace dokuwiki\plugin\statistics\test;
4
5use DokuWikiTest;
6use dokuwiki\plugin\statistics\Logger;
7use helper_plugin_statistics;
8
9/**
10 * Tests for the statistics plugin Logger class
11 *
12 * @group plugin_statistics
13 * @group plugins
14 */
15class LoggerTest extends DokuWikiTest
16{
17    protected $pluginsEnabled = ['statistics', 'sqlite'];
18
19    /** @var helper_plugin_statistics */
20    protected $helper;
21
22    /** @var Logger */
23    protected $logger;
24
25    public function setUp(): void
26    {
27        parent::setUp();
28
29        // Load the helper plugin
30        $this->helper = plugin_load('helper', 'statistics');
31
32        // Mock user agent to avoid bot detection
33        $_SERVER['HTTP_USER_AGENT'] = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36';
34
35        // Initialize logger
36        $this->logger = new Logger($this->helper);
37    }
38
39    public function tearDown(): void
40    {
41        unset($_SERVER['HTTP_USER_AGENT']);
42        parent::tearDown();
43    }
44
45    /**
46     * Test constructor initializes properties correctly
47     */
48    public function testConstructor()
49    {
50        $this->assertInstanceOf(Logger::class, $this->logger);
51
52        // Test that bot user agents throw exception
53        $_SERVER['HTTP_USER_AGENT'] = 'Googlebot/2.1 (+http://www.google.com/bot.html)';
54
55        $this->expectException(\RuntimeException::class);
56        $this->expectExceptionMessage('Bot detected, not logging');
57        new Logger($this->helper);
58    }
59
60    /**
61     * Test begin and end transaction methods
62     */
63    public function testBeginEnd()
64    {
65        $this->logger->begin();
66
67        // Verify transaction is active by checking PDO
68        $pdo = $this->helper->getDB()->getPdo();
69        $this->assertTrue($pdo->inTransaction());
70
71        $this->logger->end();
72
73        // Verify transaction is committed
74        $this->assertFalse($pdo->inTransaction());
75    }
76
77    /**
78     * Test logLastseen method
79     */
80    public function testLogLastseen()
81    {
82        global $INPUT;
83
84        // Test with no user (should not log)
85        $INPUT->server->set('REMOTE_USER', '');
86        $this->logger->logLastseen();
87
88        $count = $this->helper->getDB()->queryValue('SELECT COUNT(*) FROM lastseen');
89        $this->assertEquals(0, $count);
90
91        // Test with user
92        $INPUT->server->set('REMOTE_USER', 'testuser');
93        $this->logger->logLastseen();
94
95        $count = $this->helper->getDB()->queryValue('SELECT COUNT(*) FROM lastseen');
96        $this->assertEquals(1, $count);
97
98        $user = $this->helper->getDB()->queryValue('SELECT user FROM lastseen WHERE user = ?', ['testuser']);
99        $this->assertEquals('testuser', $user);
100    }
101
102    /**
103     * Data provider for logGroups test
104     */
105    public function logGroupsProvider()
106    {
107        return [
108            'empty groups' => [[], 'view', 0],
109            'single group' => [['admin'], 'view', 1],
110            'multiple groups' => [['admin', 'user'], 'edit', 2],
111            'filtered groups' => [['admin', 'nonexistent'], 'view', 1], // assuming only 'admin' is configured
112        ];
113    }
114
115    /**
116     * Test logGroups method
117     * @dataProvider logGroupsProvider
118     */
119    public function testLogGroups($groups, $type, $expectedCount)
120    {
121        global $conf;
122        $conf['plugin']['statistics']['loggroups'] = ['admin', 'user'];
123
124        $this->logger->logGroups($type, $groups);
125
126        $count = $this->helper->getDB()->queryValue('SELECT COUNT(*) FROM groups WHERE type = ?', [$type]);
127        $this->assertEquals($expectedCount, $count);
128
129        if ($expectedCount > 0) {
130            $loggedGroups = $this->helper->getDB()->queryAll('SELECT `group` FROM groups WHERE type = ?', [$type]);
131            $this->assertCount($expectedCount, $loggedGroups);
132        }
133    }
134
135    /**
136     * Data provider for logExternalSearch test
137     */
138    public function logExternalSearchProvider()
139    {
140        return [
141            'google search' => [
142                'https://www.google.com/search?q=dokuwiki+test',
143                'search',
144                'dokuwiki test',
145                'google'
146            ],
147            'non-search referer' => [
148                'https://example.com/page',
149                '',
150                null,
151                null
152            ],
153        ];
154    }
155
156    /**
157     * Test logExternalSearch method
158     * @dataProvider logExternalSearchProvider
159     */
160    public function testLogExternalSearch($referer, $expectedType, $expectedQuery, $expectedEngine)
161    {
162        global $INPUT;
163        $INPUT->set('p', 'test:page');
164
165        $type = '';
166        $this->logger->logExternalSearch($referer, $type);
167
168        $this->assertEquals($expectedType, $type);
169
170        if ($expectedType === 'search') {
171            $searchCount = $this->helper->getDB()->queryValue('SELECT COUNT(*) FROM search');
172            $this->assertEquals(1, $searchCount);
173
174            $search = $this->helper->getDB()->queryRecord('SELECT * FROM search ORDER BY dt DESC LIMIT 1');
175            $this->assertEquals($expectedQuery, $search['query']);
176            $this->assertEquals($expectedEngine, $search['engine']);
177        }
178    }
179
180    /**
181     * Test logSearch method
182     */
183    public function testLogSearch()
184    {
185        $page = 'test:page';
186        $query = 'test search query';
187        $words = ['test', 'search', 'query'];
188        $engine = 'Google';
189
190        $this->logger->logSearch($page, $query, $words, $engine);
191
192        // Check search table
193        $search = $this->helper->getDB()->queryRecord('SELECT * FROM search ORDER BY dt DESC LIMIT 1');
194        $this->assertEquals($page, $search['page']);
195        $this->assertEquals($query, $search['query']);
196        $this->assertEquals($engine, $search['engine']);
197
198        // Check searchwords table
199        $wordCount = $this->helper->getDB()->queryValue('SELECT COUNT(*) FROM searchwords WHERE sid = ?', [$search['id']]);
200        $this->assertEquals(3, $wordCount);
201
202        $loggedWords = $this->helper->getDB()->queryAll('SELECT word FROM searchwords WHERE sid = ? ORDER BY word', [$search['id']]);
203        $this->assertEquals(['query', 'search', 'test'], array_column($loggedWords, 'word'));
204    }
205
206    /**
207     * Test logSession method
208     */
209    public function testLogSession()
210    {
211        // Test without adding view
212        $this->logger->logSession(0);
213
214        $sessionCount = $this->helper->getDB()->queryValue('SELECT COUNT(*) FROM session');
215        $this->assertEquals(1, $sessionCount);
216
217        $session = $this->helper->getDB()->queryRecord('SELECT * FROM session ORDER BY dt DESC LIMIT 1');
218        $this->assertEquals(0, $session['views']);
219
220        // Test adding view
221        $this->logger->logSession(1);
222
223        $session = $this->helper->getDB()->queryRecord('SELECT * FROM session ORDER BY dt DESC LIMIT 1');
224        $this->assertEquals(1, $session['views']);
225
226        // Test incrementing views
227        $this->logger->logSession(1);
228
229        $session = $this->helper->getDB()->queryRecord('SELECT * FROM session ORDER BY dt DESC LIMIT 1');
230        $this->assertEquals(2, $session['views']);
231    }
232
233    /**
234     * Test logIp method
235     */
236    public function testLogIp()
237    {
238        $ip = '8.8.8.8';
239
240        // Mock HTTP client response
241        $this->markTestSkipped('Requires mocking HTTP client for external API call');
242
243        // This test would need to mock the DokuHTTPClient to avoid actual API calls
244        // For now, we'll skip it as the requirement was not to mock anything
245    }
246
247    /**
248     * Test logOutgoing method
249     */
250    public function testLogOutgoing()
251    {
252        global $INPUT;
253
254        // Test without outgoing link
255        $INPUT->set('ol', '');
256        $this->logger->logOutgoing();
257
258        $count = $this->helper->getDB()->queryValue('SELECT COUNT(*) FROM outlinks');
259        $this->assertEquals(0, $count);
260
261        // Test with outgoing link
262        $link = 'https://example.com';
263        $page = 'test:page';
264        $INPUT->set('ol', $link);
265        $INPUT->set('p', $page);
266
267        $this->logger->logOutgoing();
268
269        $count = $this->helper->getDB()->queryValue('SELECT COUNT(*) FROM outlinks');
270        $this->assertEquals(1, $count);
271
272        $outlink = $this->helper->getDB()->queryRecord('SELECT * FROM outlinks ORDER BY dt DESC LIMIT 1');
273        $this->assertEquals($link, $outlink['link']);
274        $this->assertEquals(md5($link), $outlink['link_md5']);
275        $this->assertEquals($page, $outlink['page']);
276    }
277
278    /**
279     * Test logAccess method
280     */
281    public function testLogAccess()
282    {
283        global $INPUT, $USERINFO, $conf;
284
285        $conf['plugin']['statistics']['loggroups'] = ['admin', 'user'];
286
287        $page = 'test:page';
288        $referer = 'https://example.com';
289        $user = 'testuser';
290
291        $INPUT->set('p', $page);
292        $INPUT->set('r', $referer);
293        $INPUT->set('sx', 1920);
294        $INPUT->set('sy', 1080);
295        $INPUT->set('vx', 1200);
296        $INPUT->set('vy', 800);
297        $INPUT->set('js', 1);
298        $INPUT->server->set('REMOTE_USER', $user);
299
300        $USERINFO = ['grps' => ['admin', 'user']];
301
302        $this->logger->logAccess();
303
304        // Check access table
305        $access = $this->helper->getDB()->queryRecord('SELECT * FROM access ORDER BY dt DESC LIMIT 1');
306        $this->assertEquals($page, $access['page']);
307        $this->assertEquals($user, $access['user']);
308        $this->assertEquals(1920, $access['screen_x']);
309        $this->assertEquals(1080, $access['screen_y']);
310        $this->assertEquals(1200, $access['view_x']);
311        $this->assertEquals(800, $access['view_y']);
312        $this->assertEquals(1, $access['js']);
313        $this->assertEquals('external', $access['ref_type']);
314
315        // Check refseen table
316        $refCount = $this->helper->getDB()->queryValue('SELECT COUNT(*) FROM refseen WHERE ref_md5 = ?', [md5($referer)]);
317        $this->assertEquals(1, $refCount);
318
319        // Check groups table
320        $groupCount = $this->helper->getDB()->queryValue('SELECT COUNT(*) FROM groups WHERE type = ?', ['view']);
321        $this->assertEquals(2, $groupCount);
322    }
323
324    /**
325     * Data provider for logMedia test
326     */
327    public function logMediaProvider()
328    {
329        return [
330            'image inline' => ['test.jpg', 'image/jpeg', true, 1024],
331            'video not inline' => ['test.mp4', 'video/mp4', false, 2048],
332            'document' => ['test.pdf', 'application/pdf', false, 512],
333        ];
334    }
335
336    /**
337     * Test logMedia method
338     * @dataProvider logMediaProvider
339     */
340    public function testLogMedia($media, $mime, $inline, $size)
341    {
342        global $INPUT;
343
344        $user = 'testuser';
345        $INPUT->server->set('REMOTE_USER', $user);
346
347        $this->logger->logMedia($media, $mime, $inline, $size);
348
349        $mediaLog = $this->helper->getDB()->queryRecord('SELECT * FROM media ORDER BY dt DESC LIMIT 1');
350        $this->assertEquals($media, $mediaLog['media']);
351        $this->assertEquals($user, $mediaLog['user']);
352        $this->assertEquals($size, $mediaLog['size']);
353        $this->assertEquals($inline ? 1 : 0, $mediaLog['inline']);
354
355        [$mime1, $mime2] = explode('/', strtolower($mime));
356        $this->assertEquals($mime1, $mediaLog['mime1']);
357        $this->assertEquals($mime2, $mediaLog['mime2']);
358    }
359
360    /**
361     * Data provider for logEdit test
362     */
363    public function logEditProvider()
364    {
365        return [
366            'create page' => ['new:page', 'create'],
367            'edit page' => ['existing:page', 'edit'],
368            'delete page' => ['old:page', 'delete'],
369        ];
370    }
371
372    /**
373     * Test logEdit method
374     * @dataProvider logEditProvider
375     */
376    public function testLogEdit($page, $type)
377    {
378        global $INPUT, $USERINFO, $conf;
379
380        $conf['plugin']['statistics']['loggroups'] = ['admin'];
381
382        $user = 'testuser';
383        $INPUT->server->set('REMOTE_USER', $user);
384        $USERINFO = ['grps' => ['admin']];
385
386        $this->logger->logEdit($page, $type);
387
388        // Check edits table
389        $edit = $this->helper->getDB()->queryRecord('SELECT * FROM edits ORDER BY dt DESC LIMIT 1');
390        $this->assertEquals($page, $edit['page']);
391        $this->assertEquals($type, $edit['type']);
392        $this->assertEquals($user, $edit['user']);
393
394        // Check groups table
395        $groupCount = $this->helper->getDB()->queryValue('SELECT COUNT(*) FROM groups WHERE type = ?', ['edit']);
396        $this->assertEquals(1, $groupCount);
397    }
398
399    /**
400     * Data provider for logLogin test
401     */
402    public function logLoginProvider()
403    {
404        return [
405            'login' => ['login', 'testuser'],
406            'logout' => ['logout', 'testuser'],
407            'create' => ['create', 'newuser'],
408        ];
409    }
410
411    /**
412     * Test logLogin method
413     * @dataProvider logLoginProvider
414     */
415    public function testLogLogin($type, $user)
416    {
417        global $INPUT;
418
419        if ($user === 'testuser') {
420            $INPUT->server->set('REMOTE_USER', $user);
421            $this->logger->logLogin($type);
422        } else {
423            $this->logger->logLogin($type, $user);
424        }
425
426        $login = $this->helper->getDB()->queryRecord('SELECT * FROM logins ORDER BY dt DESC LIMIT 1');
427        $this->assertEquals($type, $login['type']);
428        $this->assertEquals($user, $login['user']);
429    }
430
431    /**
432     * Test logHistoryPages method
433     */
434    public function testLogHistoryPages()
435    {
436        $this->logger->logHistoryPages();
437
438        // Check that both page_count and page_size entries were created
439        $pageCount = $this->helper->getDB()->queryValue('SELECT value FROM history WHERE info = ?', ['page_count']);
440        $pageSize = $this->helper->getDB()->queryValue('SELECT value FROM history WHERE info = ?', ['page_size']);
441
442        $this->assertIsNumeric($pageCount);
443        $this->assertIsNumeric($pageSize);
444        $this->assertGreaterThanOrEqual(0, $pageCount);
445        $this->assertGreaterThanOrEqual(0, $pageSize);
446    }
447
448    /**
449     * Test logHistoryMedia method
450     */
451    public function testLogHistoryMedia()
452    {
453        $this->logger->logHistoryMedia();
454
455        // Check that both media_count and media_size entries were created
456        $mediaCount = $this->helper->getDB()->queryValue('SELECT value FROM history WHERE info = ?', ['media_count']);
457        $mediaSize = $this->helper->getDB()->queryValue('SELECT value FROM history WHERE info = ?', ['media_size']);
458
459        $this->assertIsNumeric($mediaCount);
460        $this->assertIsNumeric($mediaSize);
461        $this->assertGreaterThanOrEqual(0, $mediaCount);
462        $this->assertGreaterThanOrEqual(0, $mediaSize);
463    }
464
465    /**
466     * Test that feedreader user agents are handled correctly
467     */
468    public function testFeedReaderUserAgent()
469    {
470        $_SERVER['HTTP_USER_AGENT'] = 'FeedBurner/1.0 (http://www.FeedBurner.com)';
471
472        $logger = new Logger($this->helper);
473
474        // Use reflection to access protected property
475        $reflection = new \ReflectionClass($logger);
476        $uaTypeProperty = $reflection->getProperty('uaType');
477        $uaTypeProperty->setAccessible(true);
478
479        $this->assertEquals('feedreader', $uaTypeProperty->getValue($logger));
480    }
481
482    /**
483     * Test session logging only works for browser type
484     */
485    public function testLogSessionOnlyForBrowser()
486    {
487        // Change user agent type to feedreader using reflection
488        $reflection = new \ReflectionClass($this->logger);
489        $uaTypeProperty = $reflection->getProperty('uaType');
490        $uaTypeProperty->setAccessible(true);
491        $uaTypeProperty->setValue($this->logger, 'feedreader');
492
493        $this->logger->logSession(1);
494
495        $sessionCount = $this->helper->getDB()->queryValue('SELECT COUNT(*) FROM session');
496        $this->assertEquals(0, $sessionCount);
497    }
498}
499