queryLogger = new QueryLogger(); $this->detector = new NPlusOneDetector(); $this->analyzer = new EagerLoadingAnalyzer(); $this->logger = $this->createMock(Logger::class); $this->service = new NPlusOneDetectionService( $this->queryLogger, $this->detector, $this->analyzer, $this->logger ); }); it('starts and stops logging', function () { expect($this->queryLogger->isEnabled())->toBeFalse(); $this->service->startLogging(); expect($this->queryLogger->isEnabled())->toBeTrue(); $this->service->stopLogging(); expect($this->queryLogger->isEnabled())->toBeFalse(); }); it('analyzes logged queries', function () { // Enable logging $this->service->startLogging(); // Simulate N+1 queries for ($i = 1; $i <= 10; $i++) { $this->queryLogger->logQuery( sql: 'SELECT * FROM posts WHERE user_id = ?', bindings: [$i], executionTimeMs: 5.0, rowCount: 1 ); } // Analyze $result = $this->service->analyze(); expect($result)->toHaveKeys(['detections', 'strategies', 'statistics']); expect($result['detections'])->not->toBeEmpty(); expect($result['statistics']['total_queries'])->toBe(10); }); it('generates formatted report', function () { $this->service->startLogging(); // Simulate N+1 queries for ($i = 1; $i <= 10; $i++) { $this->queryLogger->logQuery( sql: 'SELECT * FROM posts WHERE user_id = ?', bindings: [$i], executionTimeMs: 5.0, rowCount: 1 ); } $report = $this->service->analyzeAndReport(); expect($report)->toContain('N+1 Query Detection Report'); expect($report)->toContain('Query Statistics'); expect($report)->toContain('N+1 Problems Detected'); }); it('profiles code execution', function () { $executedCallable = false; $analysis = $this->service->profile(function () use (&$executedCallable) { $executedCallable = true; return 'result'; }); expect($executedCallable)->toBeTrue(); expect($analysis)->toHaveKey('execution_time_ms'); expect($analysis)->toHaveKey('callback_result'); expect($analysis['callback_result'])->toBe('result'); }); it('returns critical problems only', function () { $this->service->startLogging(); // Add critical N+1 (high execution count) for ($i = 1; $i <= 50; $i++) { $this->queryLogger->logQuery( sql: 'SELECT * FROM posts WHERE user_id = ?', bindings: [$i], executionTimeMs: 10.0, rowCount: 1 ); } // Add low severity N+1 for ($i = 1; $i <= 6; $i++) { $this->queryLogger->logQuery( sql: 'SELECT * FROM comments WHERE post_id = ?', bindings: [$i], executionTimeMs: 1.0, rowCount: 1 ); } $critical = $this->service->getCriticalProblems(); // Should only contain the high-severity posts pattern expect($critical)->toHaveCount(1); expect($critical[0]->isCritical())->toBeTrue(); }); it('quick check detects N+1 problems', function () { $this->service->startLogging(); for ($i = 1; $i <= 10; $i++) { $this->queryLogger->logQuery( sql: 'SELECT * FROM posts WHERE user_id = ?', bindings: [$i], executionTimeMs: 5.0, rowCount: 1 ); } expect($this->service->hasNPlusOneProblems())->toBeTrue(); }); it('returns query statistics', function () { $this->service->startLogging(); $this->queryLogger->logQuery('SELECT * FROM users', [], 5.0, 10); $this->queryLogger->logQuery('INSERT INTO logs (message) VALUES (?)', ['test'], 2.0, 1); $this->queryLogger->logQuery('UPDATE users SET name = ? WHERE id = ?', ['John', 1], 3.0, 1); $stats = $this->service->getQueryStatistics(); expect($stats['total_queries'])->toBe(3); expect($stats['select_count'])->toBe(1); expect($stats['insert_count'])->toBe(1); expect($stats['update_count'])->toBe(1); }); it('clears query logs', function () { $this->service->startLogging(); $this->queryLogger->logQuery('SELECT * FROM users', [], 5.0); expect($this->service->getQueryLogs())->toHaveCount(1); $this->service->clearLogs(); expect($this->service->getQueryLogs())->toBeEmpty(); }); it('integrates detections with eager loading strategies', function () { $this->service->startLogging(); // Create N+1 pattern for ($i = 1; $i <= 15; $i++) { $this->queryLogger->logQuery( sql: 'SELECT * FROM posts WHERE user_id = ?', bindings: [$i], executionTimeMs: 8.0, rowCount: 1 ); } $result = $this->service->analyze(); expect($result['detections'])->toHaveCount(1); expect($result['strategies'])->toHaveCount(1); $strategy = $result['strategies'][0]; expect($strategy->tableName)->toBe('posts'); expect($strategy->codeExample)->toContain('eager loading'); }); it('handles empty query logs gracefully', function () { $this->service->startLogging(); $result = $this->service->analyze(); expect($result['detections'])->toBeEmpty(); expect($result['strategies'])->toBeEmpty(); expect($result['statistics']['total_queries'])->toBe(0); }); it('logs analysis completion', function () { $this->logger->expects($this->once()) ->method('info') ->with( $this->stringContains('Analysis completed'), $this->anything() ); $this->service->startLogging(); $this->queryLogger->logQuery('SELECT * FROM users', [], 5.0); $this->service->analyze(); }); });