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