- Add comprehensive health check system with multiple endpoints - Add Prometheus metrics endpoint - Add production logging configurations (5 strategies) - Add complete deployment documentation suite: * QUICKSTART.md - 30-minute deployment guide * DEPLOYMENT_CHECKLIST.md - Printable verification checklist * DEPLOYMENT_WORKFLOW.md - Complete deployment lifecycle * PRODUCTION_DEPLOYMENT.md - Comprehensive technical reference * production-logging.md - Logging configuration guide * ANSIBLE_DEPLOYMENT.md - Infrastructure as Code automation * README.md - Navigation hub * DEPLOYMENT_SUMMARY.md - Executive summary - Add deployment scripts and automation - Add DEPLOYMENT_PLAN.md - Concrete plan for immediate deployment - Update README with production-ready features All production infrastructure is now complete and ready for deployment.
184 lines
6.3 KiB
PHP
184 lines
6.3 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
use App\Framework\Database\QueryOptimization\ValueObjects\QueryLog;
|
|
use App\Framework\Database\QueryOptimization\ValueObjects\QueryPattern;
|
|
|
|
describe('QueryPattern Value Object', function () {
|
|
it('groups queries by pattern', function () {
|
|
$queries = [
|
|
new QueryLog('SELECT * FROM users WHERE id = ?', [1], 5.0, ''),
|
|
new QueryLog('SELECT * FROM users WHERE id = ?', [2], 4.5, ''),
|
|
new QueryLog('SELECT * FROM users WHERE id = ?', [3], 6.0, ''),
|
|
];
|
|
|
|
$pattern = new QueryPattern('SELECT * FROM users WHERE id = ?', $queries);
|
|
|
|
expect($pattern->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)
|
|
for ($i = 1; $i <= 20; $i++) {
|
|
$queries[] = new QueryLog(
|
|
sql: 'SELECT * FROM posts WHERE user_id = ?',
|
|
bindings: [$i],
|
|
executionTimeMs: 10.0,
|
|
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 = [];
|
|
|
|
for ($i = 1; $i <= 50; $i++) {
|
|
$highSeverityQueries[] = new QueryLog(
|
|
sql: 'SELECT * FROM posts WHERE user_id = ?',
|
|
bindings: [$i],
|
|
executionTimeMs: 15.0,
|
|
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');
|
|
});
|
|
});
|