value)->toBe('myapp_production'); expect($dbName->toString())->toBe('myapp_production'); }); it('validates database name format', function () { // Valid names - should not throw DatabaseName::fromString('myapp'); DatabaseName::fromString('my_app_db'); DatabaseName::fromString('_temp_db'); DatabaseName::fromString('db123'); expect(true)->toBeTrue(); // Validation passed // Invalid names - should throw try { DatabaseName::fromString(''); expect(false)->toBeTrue('Should have thrown for empty name'); } catch (\InvalidArgumentException $e) { expect($e->getMessage())->toContain('cannot be empty'); } try { DatabaseName::fromString('123invalid'); expect(false)->toBeTrue('Should have thrown for name starting with number'); } catch (\InvalidArgumentException $e) { expect($e->getMessage())->toContain('must start with a letter or underscore'); } try { DatabaseName::fromString('invalid-name'); expect(false)->toBeTrue('Should have thrown for name with hyphen'); } catch (\InvalidArgumentException $e) { expect($e->getMessage())->toContain('can only contain letters, numbers, and underscores'); } try { DatabaseName::fromString('invalid name'); expect(false)->toBeTrue('Should have thrown for name with space'); } catch (\InvalidArgumentException $e) { expect($e->getMessage())->toContain('can only contain letters, numbers, and underscores'); } }); it('validates maximum length', function () { // Valid length $validName = str_repeat('a', 64); DatabaseName::fromString($validName); // Should not throw // Too long $tooLong = str_repeat('a', 65); try { DatabaseName::fromString($tooLong); expect(false)->toBeTrue('Should have thrown for name exceeding max length'); } catch (\InvalidArgumentException $e) { expect($e->getMessage())->toContain('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.) try { DatabaseName::fromString("myapp'; DROP DATABASE--"); expect(false)->toBeTrue('Should have thrown for SQL injection attempt'); } catch (\InvalidArgumentException $e) { expect($e->getMessage())->toBeString(); } try { DatabaseName::fromString('myapp UNION SELECT'); expect(false)->toBeTrue('Should have thrown for SQL injection attempt'); } catch (\InvalidArgumentException $e) { expect($e->getMessage())->toBeString(); } try { DatabaseName::fromString('myapp/*comment*/'); expect(false)->toBeTrue('Should have thrown for SQL injection attempt'); } catch (\InvalidArgumentException $e) { expect($e->getMessage())->toBeString(); } }); it('quotes database names for different platforms', function () { $dbName = DatabaseName::fromString('myapp'); expect($dbName->quoted('mysql'))->toBe('`myapp`'); expect($dbName->quoted('postgresql'))->toBe('"myapp"'); expect($dbName->quoted('postgres'))->toBe('"myapp"'); expect($dbName->quoted('pgsql'))->toBe('"myapp"'); expect($dbName->quoted('sqlite'))->toBe('"myapp"'); expect($dbName->quoted())->toBe('`myapp`'); // Default MySQL expect($dbName->quoted('unknown'))->toBe('`myapp`'); // Fallback to MySQL }); it('compares database names for equality', function () { $db1 = DatabaseName::fromString('myapp'); $db2 = DatabaseName::fromString('myapp'); $db3 = DatabaseName::fromString('MYAPP'); // Different case $db4 = DatabaseName::fromString('other_db'); expect($db1->equals($db2))->toBeTrue(); expect($db1->equals($db3))->toBeTrue(); // Case-insensitive expect($db1->equals($db4))->toBeFalse(); }); it('matches database name patterns', function () { $dbName = DatabaseName::fromString('myapp_production'); expect($dbName->matches('myapp_*'))->toBeTrue(); expect($dbName->matches('*_production'))->toBeTrue(); expect($dbName->matches('myapp_production'))->toBeTrue(); expect($dbName->matches('other_*'))->toBeFalse(); }); it('detects reserved SQL keywords', function () { $dbName = DatabaseName::fromString('myapp'); expect($dbName->isReservedKeyword())->toBeFalse(); }); it('converts to lowercase', function () { $dbName = DatabaseName::fromString('MyApp_Production'); expect($dbName->toLower())->toBe('myapp_production'); }); it('converts to uppercase', function () { $dbName = DatabaseName::fromString('myapp_production'); expect($dbName->toUpper())->toBe('MYAPP_PRODUCTION'); }); it('checks for database name prefix', function () { $dbName = DatabaseName::fromString('myapp_production'); expect($dbName->hasPrefix('myapp_'))->toBeTrue(); expect($dbName->hasPrefix('other_'))->toBeFalse(); }); it('checks for database name suffix', function () { $dbName = DatabaseName::fromString('myapp_production'); expect($dbName->hasSuffix('_production'))->toBeTrue(); expect($dbName->hasSuffix('_staging'))->toBeFalse(); }); it('adds prefix to database name', function () { $dbName = DatabaseName::fromString('myapp'); $prefixed = $dbName->withPrefix('dev_'); expect($prefixed->value)->toBe('dev_myapp'); expect($prefixed->toString())->toBe('dev_myapp'); // Original unchanged (immutable) expect($dbName->value)->toBe('myapp'); }); it('removes prefix from database name', function () { $dbName = DatabaseName::fromString('dev_myapp'); $unprefixed = $dbName->withoutPrefix('dev_'); expect($unprefixed->value)->toBe('myapp'); // Removing non-existent prefix returns same instance $same = $unprefixed->withoutPrefix('prod_'); expect($same->value)->toBe('myapp'); }); it('adds suffix to database name', function () { $dbName = DatabaseName::fromString('myapp'); $suffixed = $dbName->withSuffix('_production'); expect($suffixed->value)->toBe('myapp_production'); // Original unchanged (immutable) expect($dbName->value)->toBe('myapp'); }); it('removes suffix from database name', function () { $dbName = DatabaseName::fromString('myapp_production'); $unsuffixed = $dbName->withoutSuffix('_production'); expect($unsuffixed->value)->toBe('myapp'); // Removing non-existent suffix returns same instance $same = $unsuffixed->withoutSuffix('_staging'); expect($same->value)->toBe('myapp'); }); it('detects environment suffixes', function () { $prodDb = DatabaseName::fromString('myapp_production'); expect($prodDb->getEnvironmentSuffix())->toBe('production'); $stagingDb = DatabaseName::fromString('myapp_staging'); expect($stagingDb->getEnvironmentSuffix())->toBe('staging'); $testDb = DatabaseName::fromString('myapp_test'); expect($testDb->getEnvironmentSuffix())->toBe('test'); $devDb = DatabaseName::fromString('myapp_development'); expect($devDb->getEnvironmentSuffix())->toBe('development'); $localDb = DatabaseName::fromString('myapp_local'); expect($localDb->getEnvironmentSuffix())->toBe('local'); $noEnvDb = DatabaseName::fromString('myapp'); expect($noEnvDb->getEnvironmentSuffix())->toBeNull(); }); it('detects test databases', function () { $testDb1 = DatabaseName::fromString('myapp_test'); expect($testDb1->isTestDatabase())->toBeTrue(); $testDb2 = DatabaseName::fromString('test_myapp'); expect($testDb2->isTestDatabase())->toBeTrue(); $prodDb = DatabaseName::fromString('myapp_production'); expect($prodDb->isTestDatabase())->toBeFalse(); }); it('converts to string via magic method', function () { $dbName = DatabaseName::fromString('myapp'); expect((string) $dbName)->toBe('myapp'); }); it('is immutable', function () { $original = DatabaseName::fromString('myapp'); $prefixed = $original->withPrefix('dev_'); $suffixed = $original->withSuffix('_prod'); // Original remains unchanged expect($original->value)->toBe('myapp'); // New instances created - values should differ expect($prefixed->value)->toBe('dev_myapp'); expect($suffixed->value)->toBe('myapp_prod'); // Verify immutability by checking original hasn't changed expect($original->value)->toBe('myapp'); }); it('handles edge cases correctly', function () { // Single character (valid if letter or underscore) $a = DatabaseName::fromString('a'); expect($a->value)->toBe('a'); $underscore = DatabaseName::fromString('_'); expect($underscore->value)->toBe('_'); // Underscore-only prefix $dbName = DatabaseName::fromString('_temp_myapp'); expect($dbName->value)->toBe('_temp_myapp'); // Numbers in name (but not at start) $dbName = DatabaseName::fromString('myapp_123_db'); expect($dbName->value)->toBe('myapp_123_db'); }); });