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