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