- 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.
112 lines
4.1 KiB
PHP
112 lines
4.1 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
use App\Framework\Database\Indexing\UnusedIndexDetector;
|
|
use App\Framework\Database\Indexing\IndexAnalyzer;
|
|
use App\Framework\Database\Indexing\IndexUsageTracker;
|
|
use App\Framework\Database\PdoConnection;
|
|
|
|
describe('UnusedIndexDetector', function () {
|
|
beforeEach(function () {
|
|
$pdo = new PDO('sqlite::memory:');
|
|
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
|
|
|
|
$pdo->exec('
|
|
CREATE TABLE products (
|
|
id INTEGER PRIMARY KEY,
|
|
name TEXT UNIQUE,
|
|
category TEXT,
|
|
price REAL,
|
|
stock INTEGER,
|
|
created_at TEXT
|
|
)
|
|
');
|
|
|
|
$pdo->exec('CREATE INDEX idx_products_category ON products(category)');
|
|
$pdo->exec('CREATE INDEX idx_products_price ON products(price)');
|
|
$pdo->exec('CREATE INDEX idx_products_stock ON products(stock)');
|
|
$pdo->exec('CREATE INDEX idx_products_category_price ON products(category, price)');
|
|
|
|
$connection = new PdoConnection($pdo, 'sqlite');
|
|
$analyzer = new IndexAnalyzer($connection);
|
|
$cache = new \App\Framework\Cache\Driver\InMemoryCache();
|
|
$usageTracker = new IndexUsageTracker($cache, $analyzer);
|
|
|
|
$this->detector = new UnusedIndexDetector($analyzer, $usageTracker);
|
|
$this->tableName = 'products';
|
|
});
|
|
|
|
it('finds unused indexes', function () {
|
|
$unusedIndexes = $this->detector->findUnusedIndexes($this->tableName);
|
|
|
|
expect($unusedIndexes)->toBeArray();
|
|
// Since we haven't tracked any usage, all non-unique indexes should be marked as unused
|
|
expect(count($unusedIndexes))->toBeGreaterThan(0);
|
|
});
|
|
|
|
it('detects duplicate indexes', function () {
|
|
// Create duplicate index in test
|
|
// (In real scenario, we'd have actual duplicates)
|
|
$duplicates = $this->detector->findDuplicateIndexes($this->tableName);
|
|
|
|
expect($duplicates)->toBeArray();
|
|
});
|
|
|
|
it('finds redundant indexes (prefix pattern)', function () {
|
|
$redundant = $this->detector->findRedundantIndexes($this->tableName);
|
|
|
|
expect($redundant)->toBeArray();
|
|
|
|
// idx_products_category is redundant with idx_products_category_price
|
|
// because category is a prefix of [category, price]
|
|
if (!empty($redundant)) {
|
|
expect($redundant[0])->toHaveKey('redundant_index');
|
|
expect($redundant[0])->toHaveKey('covered_by');
|
|
}
|
|
});
|
|
|
|
it('generates comprehensive unused index report', function () {
|
|
$report = $this->detector->getUnusedIndexReport($this->tableName);
|
|
|
|
expect($report)->toHaveKey('unused');
|
|
expect($report)->toHaveKey('duplicates');
|
|
expect($report)->toHaveKey('redundant');
|
|
expect($report)->toHaveKey('total_removable');
|
|
expect($report)->toHaveKey('estimated_space_savings');
|
|
|
|
expect($report['unused'])->toBeArray();
|
|
expect($report['total_removable'])->toBeInt();
|
|
});
|
|
|
|
it('generates DROP statements for unused indexes', function () {
|
|
$statements = $this->detector->generateDropStatements($this->tableName);
|
|
|
|
expect($statements)->toBeArray();
|
|
|
|
foreach ($statements as $statement) {
|
|
expect($statement)->toContain('DROP INDEX');
|
|
expect($statement)->toContain('ON products');
|
|
}
|
|
});
|
|
|
|
it('never suggests dropping PRIMARY or UNIQUE indexes', function () {
|
|
$unusedIndexes = $this->detector->findUnusedIndexes($this->tableName);
|
|
|
|
// Filter for any PRIMARY or UNIQUE suggestions
|
|
$primaryOrUnique = array_filter($unusedIndexes, function ($index) {
|
|
return str_contains(strtolower($index['index_name']), 'primary') ||
|
|
str_contains(strtolower($index['index_name']), 'unique');
|
|
});
|
|
|
|
expect($primaryOrUnique)->toBeEmpty();
|
|
});
|
|
|
|
it('estimates space savings correctly', function () {
|
|
$report = $this->detector->getUnusedIndexReport($this->tableName);
|
|
|
|
expect($report['estimated_space_savings'])->toBeString();
|
|
expect($report['estimated_space_savings'])->toMatch('/(MB|GB|< 1 MB)/');
|
|
});
|
|
});
|