logger = new QueryLogger(); }); it('starts disabled by default', function () { expect($this->logger->isEnabled())->toBeFalse(); }); it('enables and disables logging', function () { $this->logger->enable(); expect($this->logger->isEnabled())->toBeTrue(); $this->logger->disable(); expect($this->logger->isEnabled())->toBeFalse(); }); it('ignores queries when disabled', function () { $this->logger->disable(); $this->logger->logQuery('SELECT * FROM users', [], 5.0); expect($this->logger->getQueryCount())->toBe(0); }); it('logs queries when enabled', function () { $this->logger->enable(); $this->logger->logQuery('SELECT * FROM users WHERE id = ?', [1], 5.5, 1); expect($this->logger->getQueryCount())->toBe(1); $logs = $this->logger->getQueryLogs(); expect($logs)->toHaveCount(1); expect($logs[0]->sql)->toBe('SELECT * FROM users WHERE id = ?'); expect($logs[0]->bindings)->toBe([1]); expect($logs[0]->executionTimeMs)->toBe(5.5); expect($logs[0]->rowCount)->toBe(1); }); it('captures stack trace', function () { $this->logger->enable(); $this->logger->logQuery('SELECT * FROM users', [], 5.0); $logs = $this->logger->getQueryLogs(); expect($logs[0]->stackTrace)->not->toBeEmpty(); }); it('identifies caller class and method', function () { $this->logger->enable(); $this->logger->logQuery('SELECT * FROM users', [], 5.0); $logs = $this->logger->getQueryLogs(); expect($logs[0]->callerClass)->not->toBeNull(); expect($logs[0]->callerMethod)->not->toBeNull(); }); it('calculates total execution time', function () { $this->logger->enable(); $this->logger->logQuery('SELECT * FROM users', [], 5.0); $this->logger->logQuery('SELECT * FROM posts', [], 10.0); $this->logger->logQuery('SELECT * FROM comments', [], 3.5); expect($this->logger->getTotalExecutionTime())->toBe(18.5); }); it('groups queries by pattern', function () { $this->logger->enable(); $this->logger->logQuery('SELECT * FROM users WHERE id = ?', [1], 5.0); $this->logger->logQuery('SELECT * FROM users WHERE id = ?', [2], 4.5); $this->logger->logQuery('SELECT * FROM posts WHERE user_id = ?', [1], 3.0); $grouped = $this->logger->getGroupedByPattern(); expect($grouped)->toHaveCount(2); expect($grouped['SELECT * FROM users WHERE id = ?'])->toHaveCount(2); expect($grouped['SELECT * FROM posts WHERE user_id = ?'])->toHaveCount(1); }); it('identifies slow queries', function () { $this->logger->enable(); $this->logger->logQuery('SELECT * FROM users', [], 50.0); $this->logger->logQuery('SELECT * FROM posts', [], 150.0); $this->logger->logQuery('SELECT * FROM comments', [], 5.0); $slowQueries = $this->logger->getSlowQueries(100.0); expect($slowQueries)->toHaveCount(1); // array_filter preserves keys, so get the first value $firstSlow = reset($slowQueries); expect($firstSlow->sql)->toContain('posts'); expect($firstSlow->executionTimeMs)->toBe(150.0); }); it('clears logged queries', function () { $this->logger->enable(); $this->logger->logQuery('SELECT * FROM users', [], 5.0); $this->logger->logQuery('SELECT * FROM posts', [], 3.0); expect($this->logger->getQueryCount())->toBe(2); $this->logger->clear(); expect($this->logger->getQueryCount())->toBe(0); expect($this->logger->getQueryLogs())->toBeEmpty(); }); it('generates query statistics', function () { $this->logger->enable(); $this->logger->logQuery('SELECT * FROM users', [], 5.0); $this->logger->logQuery('SELECT * FROM posts', [], 10.0); $this->logger->logQuery('INSERT INTO logs (message) VALUES (?)', ['test'], 2.0); $this->logger->logQuery('UPDATE users SET name = ? WHERE id = ?', ['John', 1], 3.0); $this->logger->logQuery('DELETE FROM logs WHERE id = ?', [1], 1.5); $this->logger->logQuery('SELECT * FROM users', [], 150.0); // Slow query $stats = $this->logger->getStatistics(); expect($stats['total_queries'])->toBe(6); expect($stats['total_time_ms'])->toBe(171.5); expect($stats['select_count'])->toBe(3); expect($stats['insert_count'])->toBe(1); expect($stats['update_count'])->toBe(1); expect($stats['delete_count'])->toBe(1); expect($stats['slow_queries'])->toBe(1); expect($stats['unique_patterns'])->toBe(5); }); it('calculates average execution time', function () { $this->logger->enable(); $this->logger->logQuery('SELECT * FROM users', [], 10.0); $this->logger->logQuery('SELECT * FROM posts', [], 20.0); $this->logger->logQuery('SELECT * FROM comments', [], 30.0); $stats = $this->logger->getStatistics(); expect($stats['average_time_ms'])->toBe(20.0); }); it('handles empty query logs in statistics', function () { $this->logger->enable(); $stats = $this->logger->getStatistics(); expect($stats['total_queries'])->toBe(0); expect($stats['average_time_ms'])->toBe(0.0); }); it('skips framework internals when finding caller', function () { $this->logger->enable(); $this->logger->logQuery('SELECT * FROM users', [], 5.0); $logs = $this->logger->getQueryLogs(); // QueryLogger tries to skip Framework/Database internals // When all frames are framework internals, fallback to first frame (QueryLogger itself) expect($logs[0]->callerClass)->toBe('App\\Framework\\Database\\QueryOptimization\\QueryLogger'); }); it('formats stack trace correctly', function () { $this->logger->enable(); $this->logger->logQuery('SELECT * FROM users', [], 5.0); $logs = $this->logger->getQueryLogs(); $stackTrace = $logs[0]->stackTrace; // Stack trace should contain class/method and file:line format expect($stackTrace)->toMatch('/.+\(.+:\d+\)/'); }); });