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 }); });