getExecutionCount())->toBe(3); expect($pattern->getTotalExecutionTimeMs())->toBe(15.5); expect($pattern->getAverageExecutionTimeMs())->toBe(5.166666666666667); }); it('detects potential N+1 pattern', function () { $queries = []; for ($i = 1; $i <= 10; $i++) { $queries[] = new QueryLog( sql: 'SELECT * FROM posts WHERE user_id = ?', bindings: [$i], executionTimeMs: 5.0, stackTrace: 'UserController::show' ); } $pattern = new QueryPattern('SELECT * FROM posts WHERE user_id = ?', $queries); expect($pattern->isPotentialNPlusOne())->toBeTrue(); }); it('rejects pattern with too few executions', function () { $queries = [ new QueryLog('SELECT * FROM users WHERE id = ?', [1], 5.0, ''), new QueryLog('SELECT * FROM users WHERE id = ?', [2], 5.0, ''), ]; $pattern = new QueryPattern('SELECT * FROM users WHERE id = ?', $queries); expect($pattern->isPotentialNPlusOne())->toBeFalse(); }); it('rejects pattern without WHERE clause', function () { $queries = []; for ($i = 1; $i <= 10; $i++) { $queries[] = new QueryLog( sql: 'SELECT * FROM users', bindings: [], executionTimeMs: 5.0, stackTrace: '' ); } $pattern = new QueryPattern('SELECT * FROM users', $queries); expect($pattern->isPotentialNPlusOne())->toBeFalse(); }); it('rejects pattern with JOIN', function () { $queries = []; for ($i = 1; $i <= 10; $i++) { $queries[] = new QueryLog( sql: 'SELECT * FROM users LEFT JOIN posts ON users.id = posts.user_id WHERE users.id = ?', bindings: [$i], executionTimeMs: 5.0, stackTrace: '' ); } $pattern = new QueryPattern( 'SELECT * FROM users LEFT JOIN posts ON users.id = posts.user_id WHERE users.id = ?', $queries ); expect($pattern->isPotentialNPlusOne())->toBeFalse(); }); it('calculates N+1 severity correctly', function () { $queries = []; // High execution count (20 queries) + high total time // Severity: exec_count(20)=+2, avg_time(55ms)=+3, consistent_caller=+2, total_time(1100ms)=+1 → 8 points for ($i = 1; $i <= 20; $i++) { $queries[] = new QueryLog( sql: 'SELECT * FROM posts WHERE user_id = ?', bindings: [$i], executionTimeMs: 55.0, // Increased from 10.0 to reach >6 severity stackTrace: 'UserController::show', callerClass: 'UserController', callerMethod: 'show' ); } $pattern = new QueryPattern('SELECT * FROM posts WHERE user_id = ?', $queries); $severity = $pattern->getNPlusOneSeverity(); // Should be high severity (execution count > 10, consistent caller, high total time) expect($severity)->toBeGreaterThan(6); }); it('classifies severity levels correctly', function () { $highSeverityQueries = []; // 50 queries with high execution time to reach CRITICAL or HIGH severity // Severity: exec_count(50)=+3, avg_time(55ms)=+3, consistent_caller=+2, total_time(2750ms)=+1 → 9 points (CRITICAL) for ($i = 1; $i <= 50; $i++) { $highSeverityQueries[] = new QueryLog( sql: 'SELECT * FROM posts WHERE user_id = ?', bindings: [$i], executionTimeMs: 55.0, // Increased from 15.0 to reach CRITICAL/HIGH stackTrace: 'UserController::show', callerClass: 'UserController', callerMethod: 'show' ); } $pattern = new QueryPattern('SELECT * FROM posts WHERE user_id = ?', $highSeverityQueries); expect($pattern->getSeverityLevel())->toBeIn(['CRITICAL', 'HIGH']); }); it('identifies table name', function () { $queries = [ new QueryLog('SELECT * FROM users WHERE id = ?', [1], 5.0, ''), ]; $pattern = new QueryPattern('SELECT * FROM users WHERE id = ?', $queries); expect($pattern->getTableName())->toBe('users'); }); it('checks for consistent caller', function () { $queries = []; for ($i = 1; $i <= 10; $i++) { $queries[] = new QueryLog( sql: 'SELECT * FROM posts WHERE user_id = ?', bindings: [$i], executionTimeMs: 5.0, stackTrace: '', callerClass: 'UserController', callerMethod: 'show' ); } $pattern = new QueryPattern('SELECT * FROM posts WHERE user_id = ?', $queries); expect($pattern->hasConsistentCaller())->toBeTrue(); }); it('detects inconsistent caller', function () { $queries = [ new QueryLog('SELECT * FROM posts WHERE user_id = ?', [1], 5.0, '', callerClass: 'UserController', callerMethod: 'show'), new QueryLog('SELECT * FROM posts WHERE user_id = ?', [2], 5.0, '', callerClass: 'PostController', callerMethod: 'index'), new QueryLog('SELECT * FROM posts WHERE user_id = ?', [3], 5.0, '', callerClass: 'UserController', callerMethod: 'show'), ]; $pattern = new QueryPattern('SELECT * FROM posts WHERE user_id = ?', $queries); expect($pattern->hasConsistentCaller())->toBeFalse(); }); it('gets caller locations', function () { $queries = [ new QueryLog('SELECT * FROM posts WHERE user_id = ?', [1], 5.0, '', callerClass: 'UserController', callerMethod: 'show', callerLine: 42), new QueryLog('SELECT * FROM posts WHERE user_id = ?', [2], 5.0, '', callerClass: 'UserController', callerMethod: 'show', callerLine: 42), ]; $pattern = new QueryPattern('SELECT * FROM posts WHERE user_id = ?', $queries); $locations = $pattern->getCallerLocations(); expect($locations)->toContain('UserController::show:42'); }); });