Files
michaelschiemer/tests/Unit/Framework/Database/ValueObjects/IndexNameTest.php
Michael Schiemer fc3d7e6357 feat(Production): Complete production deployment infrastructure
- 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.
2025-10-25 19:18:37 +02:00

268 lines
11 KiB
PHP

<?php
declare(strict_types=1);
use App\Framework\Database\ValueObjects\IndexName;
use App\Framework\Database\ValueObjects\TableName;
use App\Framework\Database\ValueObjects\ColumnName;
describe('IndexName', function () {
it('creates index name from string', function () {
$index = IndexName::fromString('idx_users_email');
expect($index->value)->toBe('idx_users_email');
expect($index->toString())->toBe('idx_users_email');
});
it('creates PRIMARY KEY index', function () {
$primary = IndexName::primary();
expect($primary->value)->toBe('PRIMARY');
expect($primary->isPrimary())->toBeTrue();
});
it('validates index name format', function () {
// Valid names
expect(fn() => IndexName::fromString('idx_users_email'))->not->toThrow(\InvalidArgumentException::class);
expect(fn() => IndexName::fromString('unique_users_email'))->not->toThrow(\InvalidArgumentException::class);
expect(fn() => IndexName::fromString('_temp_index'))->not->toThrow(\InvalidArgumentException::class);
expect(fn() => IndexName::fromString('index123'))->not->toThrow(\InvalidArgumentException::class);
// Invalid names
expect(fn() => IndexName::fromString(''))
->toThrow(\InvalidArgumentException::class, 'cannot be empty');
expect(fn() => IndexName::fromString('123invalid'))
->toThrow(\InvalidArgumentException::class, 'must start with a letter or underscore');
expect(fn() => IndexName::fromString('invalid-name'))
->toThrow(\InvalidArgumentException::class, 'can only contain letters, numbers, and underscores');
});
it('allows PRIMARY as special case', function () {
// PRIMARY is always valid
expect(fn() => IndexName::fromString('PRIMARY'))->not->toThrow(\InvalidArgumentException::class);
expect(fn() => IndexName::fromString('primary'))->not->toThrow(\InvalidArgumentException::class);
});
it('validates maximum length', function () {
$validName = str_repeat('a', 64);
expect(fn() => IndexName::fromString($validName))->not->toThrow(\InvalidArgumentException::class);
$tooLong = str_repeat('a', 65);
expect(fn() => IndexName::fromString($tooLong))
->toThrow(\InvalidArgumentException::class, 'exceeds maximum length');
});
it('detects SQL injection attempts', function () {
// Note: SQL injection attempts are caught by format validation
// since they contain invalid characters (quotes, hyphens, spaces, etc.)
expect(fn() => IndexName::fromString("idx'; DROP TABLE--"))
->toThrow(\InvalidArgumentException::class);
expect(fn() => IndexName::fromString('idx UNION SELECT'))
->toThrow(\InvalidArgumentException::class);
});
it('quotes index names for different platforms', function () {
$index = IndexName::fromString('idx_users_email');
expect($index->quoted('mysql'))->toBe('`idx_users_email`');
expect($index->quoted('postgresql'))->toBe('"idx_users_email"');
expect($index->quoted('postgres'))->toBe('"idx_users_email"');
expect($index->quoted('pgsql'))->toBe('"idx_users_email"');
expect($index->quoted('sqlite'))->toBe('"idx_users_email"');
expect($index->quoted())->toBe('`idx_users_email`'); // Default MySQL
});
it('never quotes PRIMARY KEY', function () {
$primary = IndexName::primary();
expect($primary->quoted('mysql'))->toBe('PRIMARY KEY');
expect($primary->quoted('postgresql'))->toBe('PRIMARY KEY');
expect($primary->quoted('sqlite'))->toBe('PRIMARY KEY');
});
it('compares index names for equality', function () {
$idx1 = IndexName::fromString('idx_users_email');
$idx2 = IndexName::fromString('idx_users_email');
$idx3 = IndexName::fromString('IDX_USERS_EMAIL'); // Different case
$idx4 = IndexName::fromString('idx_orders_id');
expect($idx1->equals($idx2))->toBeTrue();
expect($idx1->equals($idx3))->toBeTrue(); // Case-insensitive
expect($idx1->equals($idx4))->toBeFalse();
});
it('matches index name patterns', function () {
$index = IndexName::fromString('idx_users_email');
expect($index->matches('idx_*'))->toBeTrue();
expect($index->matches('*_email'))->toBeTrue();
expect($index->matches('idx_users_*'))->toBeTrue();
expect($index->matches('unique_*'))->toBeFalse();
});
it('detects reserved SQL keywords', function () {
$index = IndexName::fromString('idx_users_email');
expect($index->isReservedKeyword())->toBeFalse();
});
it('converts to lowercase', function () {
$index = IndexName::fromString('IDX_Users_Email');
expect($index->toLower())->toBe('idx_users_email');
});
it('checks for index name prefix', function () {
$index = IndexName::fromString('idx_users_email');
expect($index->hasPrefix('idx_'))->toBeTrue();
expect($index->hasPrefix('unique_'))->toBeFalse();
});
it('checks for index name suffix', function () {
$index = IndexName::fromString('idx_users_email');
expect($index->hasSuffix('_email'))->toBeTrue();
expect($index->hasSuffix('_id'))->toBeFalse();
});
it('detects unique indexes', function () {
$uniqueIdx1 = IndexName::fromString('unique_users_email');
$uniqueIdx2 = IndexName::fromString('idx_users_email_unique');
$uniqueIdx3 = IndexName::fromString('idx_unique_constraint_users');
$normalIdx = IndexName::fromString('idx_users_email');
expect($uniqueIdx1->isUniqueIndex())->toBeTrue();
expect($uniqueIdx2->isUniqueIndex())->toBeTrue();
expect($uniqueIdx3->isUniqueIndex())->toBeTrue();
expect($normalIdx->isUniqueIndex())->toBeFalse();
});
it('detects full-text indexes', function () {
$fulltextIdx1 = IndexName::fromString('fulltext_posts_content');
$fulltextIdx2 = IndexName::fromString('idx_posts_content_fulltext');
$fulltextIdx3 = IndexName::fromString('idx_fulltext_search');
$normalIdx = IndexName::fromString('idx_posts_title');
expect($fulltextIdx1->isFullTextIndex())->toBeTrue();
expect($fulltextIdx2->isFullTextIndex())->toBeTrue();
expect($fulltextIdx3->isFullTextIndex())->toBeTrue();
expect($normalIdx->isFullTextIndex())->toBeFalse();
});
it('generates conventional index names for columns', function () {
$table = TableName::fromString('users');
$emailColumn = ColumnName::fromString('email');
$index = IndexName::forColumns($table, $emailColumn);
expect($index->value)->toBe('idx_users_email');
expect($index->hasPrefix('idx_'))->toBeTrue();
});
it('generates multi-column index names', function () {
$table = TableName::fromString('users');
$firstNameColumn = ColumnName::fromString('first_name');
$lastNameColumn = ColumnName::fromString('last_name');
$index = IndexName::forColumns($table, $firstNameColumn, $lastNameColumn);
expect($index->value)->toBe('idx_users_first_name_last_name');
});
it('generates unique constraint names', function () {
$table = TableName::fromString('users');
$emailColumn = ColumnName::fromString('email');
$index = IndexName::uniqueFor($table, $emailColumn);
expect($index->value)->toBe('unique_users_email');
expect($index->isUniqueIndex())->toBeTrue();
});
it('generates multi-column unique constraint names', function () {
$table = TableName::fromString('users');
$emailColumn = ColumnName::fromString('email');
$tenantIdColumn = ColumnName::fromString('tenant_id');
$index = IndexName::uniqueFor($table, $emailColumn, $tenantIdColumn);
expect($index->value)->toBe('unique_users_email_tenant_id');
expect($index->isUniqueIndex())->toBeTrue();
});
it('generates foreign key index names', function () {
$table = TableName::fromString('orders');
$userIdColumn = ColumnName::fromString('user_id');
$index = IndexName::foreignKeyFor($table, $userIdColumn);
expect($index->value)->toBe('fk_orders_user_id');
expect($index->hasPrefix('fk_'))->toBeTrue();
});
it('converts to string via magic method', function () {
$index = IndexName::fromString('idx_users_email');
expect((string) $index)->toBe('idx_users_email');
});
it('is immutable', function () {
$index = IndexName::fromString('idx_users_email');
$value = $index->value;
// Value cannot be changed
expect($index->value)->toBe('idx_users_email');
expect($value)->toBe('idx_users_email');
});
it('handles edge cases correctly', function () {
// Single character (valid if letter or underscore)
expect(fn() => IndexName::fromString('a'))->not->toThrow(\InvalidArgumentException::class);
expect(fn() => IndexName::fromString('_'))->not->toThrow(\InvalidArgumentException::class);
// Underscore prefix
$index = IndexName::fromString('_temp_index');
expect($index->value)->toBe('_temp_index');
// Numbers in name
$index = IndexName::fromString('idx_123');
expect($index->value)->toBe('idx_123');
});
it('detects PRIMARY in different cases', function () {
$primary1 = IndexName::fromString('PRIMARY');
$primary2 = IndexName::fromString('primary');
$primary3 = IndexName::fromString('Primary');
expect($primary1->isPrimary())->toBeTrue();
expect($primary2->isPrimary())->toBeTrue();
expect($primary3->isPrimary())->toBeTrue();
});
it('factory methods create valid index names', function () {
$table = TableName::fromString('users');
$emailColumn = ColumnName::fromString('email');
// All factory methods should produce valid names
expect(fn() => IndexName::forColumns($table, $emailColumn))->not->toThrow(\InvalidArgumentException::class);
expect(fn() => IndexName::uniqueFor($table, $emailColumn))->not->toThrow(\InvalidArgumentException::class);
expect(fn() => IndexName::foreignKeyFor($table, $emailColumn))->not->toThrow(\InvalidArgumentException::class);
});
it('combines with TableName and ColumnName correctly', function () {
$table = TableName::fromString('users');
$emailColumn = ColumnName::fromString('email');
$statusColumn = ColumnName::fromString('status');
$index = IndexName::forColumns($table, $emailColumn, $statusColumn);
expect($index->value)->toContain('users');
expect($index->value)->toContain('email');
expect($index->value)->toContain('status');
});
});