Files
michaelschiemer/tests/Unit/Framework/Database/ValueObjects/TableNameTest.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

179 lines
7.1 KiB
PHP

<?php
declare(strict_types=1);
use App\Framework\Database\ValueObjects\TableName;
use App\Framework\Database\ValueObjects\ColumnName;
describe('TableName', function () {
it('creates table name from string', function () {
$tableName = TableName::fromString('users');
expect($tableName->value)->toBe('users');
expect($tableName->toString())->toBe('users');
});
it('validates table name format', function () {
// Valid names
expect(fn() => TableName::fromString('users'))->not->toThrow(\InvalidArgumentException::class);
expect(fn() => TableName::fromString('user_profiles'))->not->toThrow(\InvalidArgumentException::class);
expect(fn() => TableName::fromString('_temp_table'))->not->toThrow(\InvalidArgumentException::class);
expect(fn() => TableName::fromString('table123'))->not->toThrow(\InvalidArgumentException::class);
// Invalid names
expect(fn() => TableName::fromString(''))
->toThrow(\InvalidArgumentException::class, 'cannot be empty');
expect(fn() => TableName::fromString('123invalid'))
->toThrow(\InvalidArgumentException::class, 'must start with a letter or underscore');
expect(fn() => TableName::fromString('invalid-name'))
->toThrow(\InvalidArgumentException::class, 'can only contain letters, numbers, and underscores');
expect(fn() => TableName::fromString('invalid name'))
->toThrow(\InvalidArgumentException::class, 'can only contain letters, numbers, and underscores');
});
it('validates maximum length', function () {
$validName = str_repeat('a', 64);
expect(fn() => TableName::fromString($validName))->not->toThrow(\InvalidArgumentException::class);
$tooLong = str_repeat('a', 65);
expect(fn() => TableName::fromString($tooLong))
->toThrow(\InvalidArgumentException::class, 'exceeds maximum length');
});
it('detects SQL injection attempts', function () {
// Note: Most SQL injection attempts are caught by format validation first,
// since they contain invalid characters (quotes, hyphens, spaces, etc.)
// These all fail format validation (contain invalid characters)
expect(fn() => TableName::fromString("users'; DROP TABLE users--"))
->toThrow(\InvalidArgumentException::class);
expect(fn() => TableName::fromString('users UNION SELECT'))
->toThrow(\InvalidArgumentException::class);
expect(fn() => TableName::fromString('users/*comment*/'))
->toThrow(\InvalidArgumentException::class);
});
it('quotes table names for different platforms', function () {
$tableName = TableName::fromString('users');
expect($tableName->quoted('mysql'))->toBe('`users`');
expect($tableName->quoted('postgresql'))->toBe('"users"');
expect($tableName->quoted('postgres'))->toBe('"users"');
expect($tableName->quoted('pgsql'))->toBe('"users"');
expect($tableName->quoted('sqlite'))->toBe('"users"');
expect($tableName->quoted())->toBe('`users`'); // Default MySQL
expect($tableName->quoted('unknown'))->toBe('`users`'); // Fallback to MySQL
});
it('compares table names for equality', function () {
$table1 = TableName::fromString('users');
$table2 = TableName::fromString('users');
$table3 = TableName::fromString('USERS'); // Different case
$table4 = TableName::fromString('orders');
expect($table1->equals($table2))->toBeTrue();
expect($table1->equals($table3))->toBeTrue(); // Case-insensitive
expect($table1->equals($table4))->toBeFalse();
});
it('matches table name patterns', function () {
$table = TableName::fromString('user_profiles');
expect($table->matches('user_*'))->toBeTrue();
expect($table->matches('*_profiles'))->toBeTrue();
expect($table->matches('user_profiles'))->toBeTrue();
expect($table->matches('order_*'))->toBeFalse();
});
it('detects reserved SQL keywords', function () {
$table = TableName::fromString('users');
expect($table->isReservedKeyword())->toBeFalse();
$reservedTable = TableName::fromString('_select'); // Starts with underscore to be valid
// Note: 'select' itself is reserved, but '_select' is not
expect($reservedTable->isReservedKeyword())->toBeFalse();
});
it('converts to lowercase', function () {
$table = TableName::fromString('UserProfiles');
expect($table->toLower())->toBe('userprofiles');
});
it('checks for table name prefix', function () {
$table = TableName::fromString('wp_users');
expect($table->hasPrefix('wp_'))->toBeTrue();
expect($table->hasPrefix('drupal_'))->toBeFalse();
});
it('adds prefix to table name', function () {
$table = TableName::fromString('users');
$prefixed = $table->withPrefix('wp_');
expect($prefixed->value)->toBe('wp_users');
expect($prefixed->toString())->toBe('wp_users');
// Original unchanged (immutable)
expect($table->value)->toBe('users');
});
it('removes prefix from table name', function () {
$table = TableName::fromString('wp_users');
$unprefixed = $table->withoutPrefix('wp_');
expect($unprefixed->value)->toBe('users');
// Removing non-existent prefix returns same table
$same = $unprefixed->withoutPrefix('drupal_');
expect($same->value)->toBe('users');
});
it('converts to string via magic method', function () {
$table = TableName::fromString('users');
expect((string) $table)->toBe('users');
});
it('is immutable', function () {
$original = TableName::fromString('users');
$prefixed = $original->withPrefix('wp_');
// Original remains unchanged
expect($original->value)->toBe('users');
// New instance created when prefix is added
expect($prefixed)->not->toBe($original);
expect($prefixed->value)->toBe('wp_users');
// Removing non-existent prefix returns same instance (optimization)
$unprefixed = $original->withoutPrefix('wp_');
expect($unprefixed->value)->toBe('users');
// But removing actual prefix creates new instance
$prefixedTable = TableName::fromString('wp_users');
$removedPrefix = $prefixedTable->withoutPrefix('wp_');
expect($removedPrefix)->not->toBe($prefixedTable);
expect($removedPrefix->value)->toBe('users');
});
it('handles edge cases correctly', function () {
// Single character (valid if letter or underscore)
expect(fn() => TableName::fromString('a'))->not->toThrow(\InvalidArgumentException::class);
expect(fn() => TableName::fromString('_'))->not->toThrow(\InvalidArgumentException::class);
// Underscore-only prefix
$table = TableName::fromString('_temp_users');
expect($table->value)->toBe('_temp_users');
// Numbers in name (but not at start)
$table = TableName::fromString('table_123_test');
expect($table->value)->toBe('table_123_test');
});
});