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

350 lines
13 KiB
PHP

<?php
declare(strict_types=1);
use App\Framework\Database\ValueObjects\ConstraintName;
use App\Framework\Database\ValueObjects\TableName;
use App\Framework\Database\ValueObjects\ColumnName;
describe('ConstraintName', function () {
it('creates constraint name from string', function () {
$constraint = ConstraintName::fromString('fk_users_company_id');
expect($constraint->value)->toBe('fk_users_company_id');
expect($constraint->toString())->toBe('fk_users_company_id');
});
it('creates PRIMARY KEY constraint', function () {
$primaryKey = ConstraintName::primaryKey();
expect($primaryKey->value)->toBe('PRIMARY KEY');
expect($primaryKey->isPrimaryKey())->toBeTrue();
});
it('creates UNIQUE constraint with conventional naming', function () {
$table = TableName::fromString('users');
$emailColumn = ColumnName::fromString('email');
$unique = ConstraintName::unique($table, $emailColumn);
expect($unique->value)->toBe('uq_users_email');
expect($unique->isUnique())->toBeTrue();
});
it('creates multi-column UNIQUE constraint', function () {
$table = TableName::fromString('users');
$emailColumn = ColumnName::fromString('email');
$tenantColumn = ColumnName::fromString('tenant_id');
$unique = ConstraintName::unique($table, $emailColumn, $tenantColumn);
expect($unique->value)->toBe('uq_users_email_tenant_id');
expect($unique->isUnique())->toBeTrue();
});
it('creates FOREIGN KEY constraint with conventional naming', function () {
$table = TableName::fromString('orders');
$column = ColumnName::fromString('user_id');
$refTable = TableName::fromString('users');
$fk = ConstraintName::foreignKey($table, $column, $refTable);
expect($fk->value)->toBe('fk_orders_user_id_users');
expect($fk->isForeignKey())->toBeTrue();
});
it('creates CHECK constraint with conventional naming', function () {
$table = TableName::fromString('products');
$check = ConstraintName::check($table, 'positive_price');
expect($check->value)->toBe('chk_products_positive_price');
expect($check->isCheck())->toBeTrue();
});
it('validates constraint name format', function () {
// Valid names - should not throw
ConstraintName::fromString('fk_users_company');
ConstraintName::fromString('uq_email');
ConstraintName::fromString('chk_positive_amount');
expect(true)->toBeTrue(); // Validation passed
// Invalid names - should throw
try {
ConstraintName::fromString('');
expect(false)->toBeTrue('Should have thrown for empty name');
} catch (\InvalidArgumentException $e) {
expect($e->getMessage())->toContain('cannot be empty');
}
try {
ConstraintName::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 {
ConstraintName::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');
}
});
it('allows keyword constraints as special cases', function () {
// These are SQL keywords but valid constraints
ConstraintName::fromString('PRIMARY KEY');
ConstraintName::fromString('UNIQUE');
ConstraintName::fromString('CHECK');
ConstraintName::fromString('FOREIGN KEY');
expect(true)->toBeTrue(); // All keyword constraints created successfully
});
it('validates maximum length', function () {
// Valid length
$validName = 'fk_' . str_repeat('a', 61);
ConstraintName::fromString($validName); // Should not throw
// Too long
$tooLong = 'fk_' . str_repeat('a', 62);
try {
ConstraintName::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 with special characters are caught by format validation
try {
ConstraintName::fromString("fk'; DROP TABLE--");
expect(false)->toBeTrue('Should have thrown for SQL injection attempt');
} catch (\InvalidArgumentException $e) {
expect($e->getMessage())->toBeString();
}
try {
ConstraintName::fromString('fk/*comment*/');
expect(false)->toBeTrue('Should have thrown for SQL comment injection');
} catch (\InvalidArgumentException $e) {
expect($e->getMessage())->toBeString();
}
});
it('quotes constraint names for different platforms', function () {
$constraint = ConstraintName::fromString('fk_users_company');
expect($constraint->quoted('mysql'))->toBe('`fk_users_company`');
expect($constraint->quoted('postgresql'))->toBe('"fk_users_company"');
expect($constraint->quoted('sqlite'))->toBe('"fk_users_company"');
expect($constraint->quoted())->toBe('`fk_users_company`'); // Default MySQL
});
it('never quotes keyword constraints', function () {
$primaryKey = ConstraintName::primaryKey();
$unique = ConstraintName::fromString('UNIQUE');
$check = ConstraintName::fromString('CHECK');
expect($primaryKey->quoted('mysql'))->toBe('PRIMARY KEY');
expect($primaryKey->quoted('postgresql'))->toBe('PRIMARY KEY');
expect($unique->quoted('mysql'))->toBe('UNIQUE');
expect($check->quoted('postgresql'))->toBe('CHECK');
});
it('compares constraint names for equality', function () {
$fk1 = ConstraintName::fromString('fk_users_company');
$fk2 = ConstraintName::fromString('fk_users_company');
$fk3 = ConstraintName::fromString('FK_USERS_COMPANY'); // Different case
$fk4 = ConstraintName::fromString('fk_orders_user');
expect($fk1->equals($fk2))->toBeTrue();
expect($fk1->equals($fk3))->toBeTrue(); // Case-insensitive
expect($fk1->equals($fk4))->toBeFalse();
});
it('matches constraint name patterns', function () {
$constraint = ConstraintName::fromString('fk_users_company_id');
expect($constraint->matches('fk_*'))->toBeTrue();
expect($constraint->matches('*_company_id'))->toBeTrue();
expect($constraint->matches('fk_users_*'))->toBeTrue();
expect($constraint->matches('uq_*'))->toBeFalse();
});
it('detects reserved SQL keywords', function () {
$constraint = ConstraintName::fromString('fk_users_company');
expect($constraint->isReservedKeyword())->toBeFalse();
});
it('converts to lowercase', function () {
$constraint = ConstraintName::fromString('FK_Users_Company');
expect($constraint->toLower())->toBe('fk_users_company');
});
it('checks for constraint name prefix', function () {
$constraint = ConstraintName::fromString('fk_users_company');
expect($constraint->hasPrefix('fk_'))->toBeTrue();
expect($constraint->hasPrefix('uq_'))->toBeFalse();
});
it('checks for constraint name suffix', function () {
$constraint = ConstraintName::fromString('fk_users_company_id');
expect($constraint->hasSuffix('_id'))->toBeTrue();
expect($constraint->hasSuffix('_key'))->toBeFalse();
});
it('detects PRIMARY KEY constraints', function () {
$pk1 = ConstraintName::primaryKey();
expect($pk1->isPrimaryKey())->toBeTrue();
$pk2 = ConstraintName::fromString('PRIMARY');
expect($pk2->isPrimaryKey())->toBeTrue();
$fk = ConstraintName::fromString('fk_users_company');
expect($fk->isPrimaryKey())->toBeFalse();
});
it('detects FOREIGN KEY constraints', function () {
$fk1 = ConstraintName::fromString('fk_users_company');
expect($fk1->isForeignKey())->toBeTrue();
$fk2 = ConstraintName::fromString('FOREIGN KEY');
expect($fk2->isForeignKey())->toBeTrue();
$uq = ConstraintName::fromString('uq_email');
expect($uq->isForeignKey())->toBeFalse();
});
it('detects UNIQUE constraints', function () {
$uq1 = ConstraintName::fromString('uq_email');
expect($uq1->isUnique())->toBeTrue();
$uq2 = ConstraintName::fromString('unique_users_email');
expect($uq2->isUnique())->toBeTrue();
$uq3 = ConstraintName::fromString('UNIQUE');
expect($uq3->isUnique())->toBeTrue();
$fk = ConstraintName::fromString('fk_users_company');
expect($fk->isUnique())->toBeFalse();
});
it('detects CHECK constraints', function () {
$chk1 = ConstraintName::fromString('chk_positive_amount');
expect($chk1->isCheck())->toBeTrue();
$chk2 = ConstraintName::fromString('check_valid_status');
expect($chk2->isCheck())->toBeTrue();
$chk3 = ConstraintName::fromString('CHECK');
expect($chk3->isCheck())->toBeTrue();
$fk = ConstraintName::fromString('fk_users_company');
expect($fk->isCheck())->toBeFalse();
});
it('detects DEFAULT constraints', function () {
$df1 = ConstraintName::fromString('df_created_at');
expect($df1->isDefault())->toBeTrue();
$df2 = ConstraintName::fromString('default_status');
expect($df2->isDefault())->toBeTrue();
$df3 = ConstraintName::fromString('DEFAULT');
expect($df3->isDefault())->toBeTrue();
$fk = ConstraintName::fromString('fk_users_company');
expect($fk->isDefault())->toBeFalse();
});
it('identifies keyword constraints', function () {
$pk = ConstraintName::primaryKey();
expect($pk->isKeywordConstraint())->toBeTrue();
$unique = ConstraintName::fromString('UNIQUE');
expect($unique->isKeywordConstraint())->toBeTrue();
$check = ConstraintName::fromString('CHECK');
expect($check->isKeywordConstraint())->toBeTrue();
$custom = ConstraintName::fromString('fk_users_company');
expect($custom->isKeywordConstraint())->toBeFalse();
});
it('determines constraint type', function () {
$pk = ConstraintName::primaryKey();
expect($pk->getType())->toBe('primary_key');
$fk = ConstraintName::fromString('fk_users_company');
expect($fk->getType())->toBe('foreign_key');
$uq = ConstraintName::fromString('uq_email');
expect($uq->getType())->toBe('unique');
$chk = ConstraintName::fromString('chk_positive');
expect($chk->getType())->toBe('check');
$df = ConstraintName::fromString('df_status');
expect($df->getType())->toBe('default');
$custom = ConstraintName::fromString('my_custom_constraint');
expect($custom->getType())->toBe('custom');
});
it('converts to string via magic method', function () {
$constraint = ConstraintName::fromString('fk_users_company');
expect((string) $constraint)->toBe('fk_users_company');
});
it('is immutable', function () {
$table = TableName::fromString('users');
$column = ColumnName::fromString('email');
$unique = ConstraintName::unique($table, $column);
$value = $unique->value;
// Value cannot be changed
expect($unique->value)->toBe('uq_users_email');
expect($value)->toBe('uq_users_email');
});
it('handles edge cases correctly', function () {
// Single character (valid if letter or underscore)
$c = ConstraintName::fromString('c');
expect($c->value)->toBe('c');
$underscore = ConstraintName::fromString('_');
expect($underscore->value)->toBe('_');
// Underscore prefix
$constraint = ConstraintName::fromString('_temp_constraint');
expect($constraint->value)->toBe('_temp_constraint');
// Numbers in name
$constraint = ConstraintName::fromString('fk_table_123');
expect($constraint->value)->toBe('fk_table_123');
});
it('factory methods create valid constraint names', function () {
$table = TableName::fromString('users');
$column = ColumnName::fromString('email');
$refTable = TableName::fromString('companies');
// All factory methods should produce valid names
ConstraintName::primaryKey();
ConstraintName::unique($table, $column);
ConstraintName::foreignKey($table, $column, $refTable);
ConstraintName::check($table, 'valid_status');
expect(true)->toBeTrue(); // All factory methods succeeded
});
});