value)->toBe('email'); expect($column->toString())->toBe('email'); }); it('validates column name format', function () { // Valid names expect(fn() => ColumnName::fromString('email'))->not->toThrow(\InvalidArgumentException::class); expect(fn() => ColumnName::fromString('user_email'))->not->toThrow(\InvalidArgumentException::class); expect(fn() => ColumnName::fromString('_temp_field'))->not->toThrow(\InvalidArgumentException::class); expect(fn() => ColumnName::fromString('column123'))->not->toThrow(\InvalidArgumentException::class); // Invalid names expect(fn() => ColumnName::fromString('')) ->toThrow(\InvalidArgumentException::class, 'cannot be empty'); expect(fn() => ColumnName::fromString('123invalid')) ->toThrow(\InvalidArgumentException::class, 'must start with a letter or underscore'); expect(fn() => ColumnName::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() => ColumnName::fromString($validName))->not->toThrow(\InvalidArgumentException::class); $tooLong = str_repeat('a', 65); expect(fn() => ColumnName::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() => ColumnName::fromString("email'; DROP TABLE--")) ->toThrow(\InvalidArgumentException::class); expect(fn() => ColumnName::fromString('email UNION SELECT')) ->toThrow(\InvalidArgumentException::class); }); it('quotes column names for different platforms', function () { $column = ColumnName::fromString('email'); expect($column->quoted('mysql'))->toBe('`email`'); expect($column->quoted('postgresql'))->toBe('"email"'); expect($column->quoted('postgres'))->toBe('"email"'); expect($column->quoted('pgsql'))->toBe('"email"'); expect($column->quoted('sqlite'))->toBe('"email"'); expect($column->quoted())->toBe('`email`'); // Default MySQL }); it('creates qualified column name', function () { $table = TableName::fromString('users'); $column = ColumnName::fromString('email'); expect($column->qualified($table, 'mysql'))->toBe('`users`.`email`'); expect($column->qualified($table, 'postgresql'))->toBe('"users"."email"'); expect($column->qualified($table, 'sqlite'))->toBe('"users"."email"'); }); it('compares column names for equality', function () { $col1 = ColumnName::fromString('email'); $col2 = ColumnName::fromString('email'); $col3 = ColumnName::fromString('EMAIL'); // Different case $col4 = ColumnName::fromString('username'); expect($col1->equals($col2))->toBeTrue(); expect($col1->equals($col3))->toBeTrue(); // Case-insensitive expect($col1->equals($col4))->toBeFalse(); }); it('matches column name patterns', function () { $column = ColumnName::fromString('user_email'); expect($column->matches('user_*'))->toBeTrue(); expect($column->matches('*_email'))->toBeTrue(); expect($column->matches('user_email'))->toBeTrue(); expect($column->matches('order_*'))->toBeFalse(); }); it('detects reserved SQL keywords', function () { $column = ColumnName::fromString('email'); expect($column->isReservedKeyword())->toBeFalse(); }); it('converts to lowercase', function () { $column = ColumnName::fromString('UserEmail'); expect($column->toLower())->toBe('useremail'); }); it('checks for column name suffix', function () { $column = ColumnName::fromString('user_id'); expect($column->hasSuffix('_id'))->toBeTrue(); expect($column->hasSuffix('_at'))->toBeFalse(); }); it('detects foreign key columns', function () { $userId = ColumnName::fromString('user_id'); $orderId = ColumnName::fromString('order_id'); $email = ColumnName::fromString('email'); $id = ColumnName::fromString('id'); // Primary key, not foreign key expect($userId->isForeignKey())->toBeTrue(); expect($orderId->isForeignKey())->toBeTrue(); expect($email->isForeignKey())->toBeFalse(); expect($id->isForeignKey())->toBeFalse(); // Special case: 'id' itself is not FK }); it('detects timestamp columns', function () { $createdAt = ColumnName::fromString('created_at'); $updatedAt = ColumnName::fromString('updated_at'); $deletedAt = ColumnName::fromString('deleted_at'); $publishedAt = ColumnName::fromString('published_at'); $email = ColumnName::fromString('email'); expect($createdAt->isTimestamp())->toBeTrue(); expect($updatedAt->isTimestamp())->toBeTrue(); expect($deletedAt->isTimestamp())->toBeTrue(); expect($publishedAt->isTimestamp())->toBeTrue(); // Any *_at expect($email->isTimestamp())->toBeFalse(); }); it('detects standard timestamp column names', function () { // Standard Laravel/framework timestamp columns $createdAt = ColumnName::fromString('created_at'); $updatedAt = ColumnName::fromString('updated_at'); $deletedAt = ColumnName::fromString('deleted_at'); expect($createdAt->isTimestamp())->toBeTrue(); expect($updatedAt->isTimestamp())->toBeTrue(); expect($deletedAt->isTimestamp())->toBeTrue(); }); it('converts to string via magic method', function () { $column = ColumnName::fromString('email'); expect((string) $column)->toBe('email'); }); it('is immutable', function () { $column = ColumnName::fromString('email'); $value = $column->value; // Value cannot be changed expect($column->value)->toBe('email'); expect($value)->toBe('email'); }); it('handles edge cases correctly', function () { // Single character (valid if letter or underscore) expect(fn() => ColumnName::fromString('a'))->not->toThrow(\InvalidArgumentException::class); expect(fn() => ColumnName::fromString('_'))->not->toThrow(\InvalidArgumentException::class); // Underscore prefix $column = ColumnName::fromString('_temp_field'); expect($column->value)->toBe('_temp_field'); // Numbers in name $column = ColumnName::fromString('field_123'); expect($column->value)->toBe('field_123'); }); it('combines with TableName correctly', function () { $table = TableName::fromString('users'); $column = ColumnName::fromString('email'); $qualified = $column->qualified($table); expect($qualified)->toContain('users'); expect($qualified)->toContain('email'); expect($qualified)->toContain('.'); }); it('detects various foreign key naming patterns', function () { // Standard pattern: table_id expect(ColumnName::fromString('user_id')->isForeignKey())->toBeTrue(); expect(ColumnName::fromString('order_id')->isForeignKey())->toBeTrue(); expect(ColumnName::fromString('product_id')->isForeignKey())->toBeTrue(); // Non-FK patterns expect(ColumnName::fromString('id')->isForeignKey())->toBeFalse(); expect(ColumnName::fromString('email')->isForeignKey())->toBeFalse(); expect(ColumnName::fromString('created_at')->isForeignKey())->toBeFalse(); }); it('detects various timestamp naming patterns', function () { // Suffixed with _at expect(ColumnName::fromString('created_at')->isTimestamp())->toBeTrue(); expect(ColumnName::fromString('updated_at')->isTimestamp())->toBeTrue(); expect(ColumnName::fromString('deleted_at')->isTimestamp())->toBeTrue(); expect(ColumnName::fromString('published_at')->isTimestamp())->toBeTrue(); expect(ColumnName::fromString('verified_at')->isTimestamp())->toBeTrue(); // Non-timestamp patterns expect(ColumnName::fromString('email')->isTimestamp())->toBeFalse(); expect(ColumnName::fromString('user_id')->isTimestamp())->toBeFalse(); expect(ColumnName::fromString('status')->isTimestamp())->toBeFalse(); }); });