fix: Gitea Traefik routing and connection pool optimization
Some checks failed
🚀 Build & Deploy Image / Determine Build Necessity (push) Failing after 10m14s
🚀 Build & Deploy Image / Build Runtime Base Image (push) Has been skipped
🚀 Build & Deploy Image / Build Docker Image (push) Has been skipped
🚀 Build & Deploy Image / Run Tests & Quality Checks (push) Has been skipped
🚀 Build & Deploy Image / Auto-deploy to Staging (push) Has been skipped
🚀 Build & Deploy Image / Auto-deploy to Production (push) Has been skipped
Security Vulnerability Scan / Check for Dependency Changes (push) Failing after 11m25s
Security Vulnerability Scan / Composer Security Audit (push) Has been cancelled

- Remove middleware reference from Gitea Traefik labels (caused routing issues)
- Optimize Gitea connection pool settings (MAX_IDLE_CONNS=30, authentication_timeout=180s)
- Add explicit service reference in Traefik labels
- Fix intermittent 504 timeouts by improving PostgreSQL connection handling

Fixes Gitea unreachability via git.michaelschiemer.de
This commit is contained in:
2025-11-09 14:46:15 +01:00
parent 85c369e846
commit 36ef2a1e2c
1366 changed files with 104925 additions and 28719 deletions

View File

@@ -0,0 +1,209 @@
<?php
declare(strict_types=1);
use App\Framework\String\ValueObjects\PosixAlphaString;
use App\Framework\String\ValueObjects\TypedString;
use App\Framework\String\ValueObjects\PosixString;
describe('PosixAlphaString', function () {
describe('Validation', function () {
it('accepts valid alphabetic strings', function () {
$alpha1 = PosixAlphaString::fromString('abcdefghijklmnopqrstuvwxyz');
$alpha2 = PosixAlphaString::fromString('ABCDEFGHIJKLMNOPQRSTUVWXYZ');
$alpha3 = PosixAlphaString::fromString('HelloWorld');
expect($alpha1->value)->toBe('abcdefghijklmnopqrstuvwxyz');
expect($alpha2->value)->toBe('ABCDEFGHIJKLMNOPQRSTUVWXYZ');
expect($alpha3->value)->toBe('HelloWorld');
});
it('rejects strings with digits', function () {
PosixAlphaString::fromString('abc123');
})->throws(\InvalidArgumentException::class, 'String must contain only POSIX alphabetic characters');
it('rejects strings with punctuation', function () {
PosixAlphaString::fromString('hello!world');
})->throws(\InvalidArgumentException::class, 'String must contain only POSIX alphabetic characters');
it('rejects strings with whitespace', function () {
PosixAlphaString::fromString('hello world');
})->throws(\InvalidArgumentException::class, 'String must contain only POSIX alphabetic characters');
it('rejects strings with hyphens', function () {
PosixAlphaString::fromString('hello-world');
})->throws(\InvalidArgumentException::class, 'String must contain only POSIX alphabetic characters');
it('rejects strings with underscores', function () {
PosixAlphaString::fromString('hello_world');
})->throws(\InvalidArgumentException::class, 'String must contain only POSIX alphabetic characters');
it('rejects empty strings', function () {
PosixAlphaString::fromString('');
})->throws(\InvalidArgumentException::class, 'String must not be empty');
it('rejects strings with special characters', function () {
PosixAlphaString::fromString('hello@world');
})->throws(\InvalidArgumentException::class, 'String must contain only POSIX alphabetic characters');
});
describe('International Character Support', function () {
it('validates German characters with locale', function () {
// Note: Requires locale to be set
// setlocale(LC_CTYPE, 'de_DE.UTF-8');
$german1 = PosixAlphaString::fromString('Müller');
$german2 = PosixAlphaString::fromString('Schön');
$german3 = PosixAlphaString::fromString('Größe');
expect($german1->value)->toBe('Müller');
expect($german2->value)->toBe('Schön');
expect($german3->value)->toBe('Größe');
})->skip('Requires locale configuration');
it('validates French characters with locale', function () {
// Note: Requires locale to be set
// setlocale(LC_CTYPE, 'fr_FR.UTF-8');
$french1 = PosixAlphaString::fromString('Dupré');
$french2 = PosixAlphaString::fromString('Château');
$french3 = PosixAlphaString::fromString('Garçon');
expect($french1->value)->toBe('Dupré');
expect($french2->value)->toBe('Château');
expect($french3->value)->toBe('Garçon');
})->skip('Requires locale configuration');
it('validates Spanish characters with locale', function () {
// Note: Requires locale to be set
// setlocale(LC_CTYPE, 'es_ES.UTF-8');
$spanish1 = PosixAlphaString::fromString('Señor');
$spanish2 = PosixAlphaString::fromString('Niño');
$spanish3 = PosixAlphaString::fromString('José');
expect($spanish1->value)->toBe('Señor');
expect($spanish2->value)->toBe('Niño');
expect($spanish3->value)->toBe('José');
})->skip('Requires locale configuration');
});
describe('Conversion Methods', function () {
it('converts to TypedString', function () {
$alpha = PosixAlphaString::fromString('Hello');
$typed = $alpha->toTypedString();
expect($typed)->toBeInstanceOf(TypedString::class);
expect($typed->value)->toBe('Hello');
expect($typed->isAlphabetic())->toBeTrue();
});
it('converts to PosixString', function () {
$alpha = PosixAlphaString::fromString('World');
$posix = $alpha->toPosixString();
expect($posix)->toBeInstanceOf(PosixString::class);
expect($posix->value)->toBe('World');
expect($posix->isAlpha())->toBeTrue();
});
it('converts to string', function () {
$alpha = PosixAlphaString::fromString('Test');
expect($alpha->toString())->toBe('Test');
expect((string) $alpha)->toBe('Test');
});
});
describe('Comparison Methods', function () {
it('checks equality', function () {
$alpha1 = PosixAlphaString::fromString('Hello');
$alpha2 = PosixAlphaString::fromString('Hello');
$alpha3 = PosixAlphaString::fromString('World');
expect($alpha1->equals($alpha2))->toBeTrue();
expect($alpha1->equals($alpha3))->toBeFalse();
});
it('compares case-sensitive', function () {
$lower = PosixAlphaString::fromString('hello');
$upper = PosixAlphaString::fromString('HELLO');
expect($lower->equals($upper))->toBeFalse();
});
});
describe('Real World Use Cases', function () {
it('validates first names', function () {
$firstName1 = PosixAlphaString::fromString('John');
$firstName2 = PosixAlphaString::fromString('Mary');
$firstName3 = PosixAlphaString::fromString('Alexander');
expect($firstName1->value)->toBe('John');
expect($firstName2->value)->toBe('Mary');
expect($firstName3->value)->toBe('Alexander');
});
it('validates last names', function () {
$lastName1 = PosixAlphaString::fromString('Smith');
$lastName2 = PosixAlphaString::fromString('Johnson');
$lastName3 = PosixAlphaString::fromString('Williams');
expect($lastName1->value)->toBe('Smith');
expect($lastName2->value)->toBe('Johnson');
expect($lastName3->value)->toBe('Williams');
});
it('rejects names with numbers', function () {
PosixAlphaString::fromString('John123');
})->throws(\InvalidArgumentException::class);
it('rejects compound names with hyphens', function () {
// Note: Real-world compound names like "Jean-Pierre" would need
// a different validation strategy or preprocessing
PosixAlphaString::fromString('Jean-Pierre');
})->throws(\InvalidArgumentException::class);
it('rejects names with titles', function () {
PosixAlphaString::fromString('Dr. Smith');
})->throws(\InvalidArgumentException::class);
});
describe('Single Character Strings', function () {
it('accepts single letter', function () {
$a = PosixAlphaString::fromString('A');
$z = PosixAlphaString::fromString('z');
expect($a->value)->toBe('A');
expect($z->value)->toBe('z');
});
it('rejects single digit', function () {
PosixAlphaString::fromString('5');
})->throws(\InvalidArgumentException::class);
it('rejects single special character', function () {
PosixAlphaString::fromString('!');
})->throws(\InvalidArgumentException::class);
});
describe('Edge Cases', function () {
it('handles very long alphabetic strings', function () {
$longString = str_repeat('abcdefghijklmnopqrstuvwxyz', 100);
$alpha = PosixAlphaString::fromString($longString);
expect($alpha->value)->toBe($longString);
expect(strlen($alpha->value))->toBe(2600);
});
it('handles mixed case correctly', function () {
$mixed = PosixAlphaString::fromString('AbCdEfGhIjKlMnOpQrStUvWxYz');
expect($mixed->value)->toBe('AbCdEfGhIjKlMnOpQrStUvWxYz');
});
it('rejects numeric-looking strings', function () {
PosixAlphaString::fromString('one23');
})->throws(\InvalidArgumentException::class);
});
});

View File

@@ -0,0 +1,278 @@
<?php
declare(strict_types=1);
use App\Framework\String\ValueObjects\PosixString;
describe('PosixString', function () {
describe('Factory Method', function () {
it('creates from string', function () {
$str = PosixString::fromString('test123');
expect($str->value)->toBe('test123');
});
});
describe('POSIX Character Class Validators', function () {
it('validates alnum (alphanumeric)', function () {
$valid = PosixString::fromString('abc123XYZ');
$invalid = PosixString::fromString('abc-123');
expect($valid->isAlnum())->toBeTrue();
expect($invalid->isAlnum())->toBeFalse();
});
it('validates alpha (alphabetic)', function () {
$valid = PosixString::fromString('abcXYZ');
$invalid = PosixString::fromString('abc123');
expect($valid->isAlpha())->toBeTrue();
expect($invalid->isAlpha())->toBeFalse();
});
it('validates alpha with international characters (locale-aware)', function () {
// Note: Locale must be set for international character support
// setlocale(LC_CTYPE, 'de_DE.UTF-8');
$german = PosixString::fromString('Müller');
$french = PosixString::fromString('Dupré');
$spanish = PosixString::fromString('Señor');
// These work if locale is set
// expect($german->isAlpha())->toBeTrue();
// expect($french->isAlpha())->toBeTrue();
// expect($spanish->isAlpha())->toBeTrue();
})->skip('Requires locale configuration');
it('validates ascii', function () {
$valid = PosixString::fromString('Hello World!');
$invalid = PosixString::fromString('Hëllö Wörld!');
expect($valid->isAscii())->toBeTrue();
expect($invalid->isAscii())->toBeFalse();
});
it('validates blank (space and tab only)', function () {
$valid = PosixString::fromString(" \t ");
$invalid = PosixString::fromString(" \n ");
expect($valid->isBlank())->toBeTrue();
expect($invalid->isBlank())->toBeFalse();
});
it('validates cntrl (control characters)', function () {
$valid = PosixString::fromString("\x00\x01\x02");
$invalid = PosixString::fromString("abc");
expect($valid->isCntrl())->toBeTrue();
expect($invalid->isCntrl())->toBeFalse();
});
it('validates digit', function () {
$valid = PosixString::fromString('123456');
$invalid = PosixString::fromString('123abc');
expect($valid->isDigit())->toBeTrue();
expect($invalid->isDigit())->toBeFalse();
});
it('validates graph (visible characters, excluding space)', function () {
$valid = PosixString::fromString('Hello!');
$invalid = PosixString::fromString('Hello World');
expect($valid->isGraph())->toBeTrue();
expect($invalid->isGraph())->toBeFalse();
});
it('validates lower (lowercase)', function () {
$valid = PosixString::fromString('hello');
$invalid = PosixString::fromString('Hello');
expect($valid->isLower())->toBeTrue();
expect($invalid->isLower())->toBeFalse();
});
it('validates print (printable, including space)', function () {
$valid = PosixString::fromString('Hello World!');
$invalid = PosixString::fromString("Hello\x00World");
expect($valid->isPrint())->toBeTrue();
expect($invalid->isPrint())->toBeFalse();
});
it('validates punct (punctuation)', function () {
$valid = PosixString::fromString('!@#$%^&*()');
$invalid = PosixString::fromString('abc!@#');
expect($valid->isPunct())->toBeTrue();
expect($invalid->isPunct())->toBeFalse();
});
it('validates space (whitespace)', function () {
$valid = PosixString::fromString(" \t\n\r");
$invalid = PosixString::fromString('abc');
expect($valid->isSpace())->toBeTrue();
expect($invalid->isSpace())->toBeFalse();
});
it('validates upper (uppercase)', function () {
$valid = PosixString::fromString('HELLO');
$invalid = PosixString::fromString('Hello');
expect($valid->isUpper())->toBeTrue();
expect($invalid->isUpper())->toBeFalse();
});
it('validates xdigit (hexadecimal)', function () {
$valid = PosixString::fromString('0123456789abcdefABCDEF');
$invalid = PosixString::fromString('0123xyz');
expect($valid->isXdigit())->toBeTrue();
expect($invalid->isXdigit())->toBeFalse();
});
it('validates word (alphanumeric + underscore)', function () {
$valid = PosixString::fromString('user_name123');
$invalid = PosixString::fromString('user-name');
expect($valid->isWord())->toBeTrue();
expect($invalid->isWord())->toBeFalse();
});
});
describe('Helper Methods', function () {
it('checks if string contains POSIX class characters', function () {
$str = PosixString::fromString('hello123world');
expect($str->containsPosixClass('alpha'))->toBeTrue();
expect($str->containsPosixClass('digit'))->toBeTrue();
expect($str->containsPosixClass('punct'))->toBeFalse();
});
it('counts POSIX class characters', function () {
$str = PosixString::fromString('abc123xyz789');
expect($str->countPosixClass('alpha'))->toBe(6);
expect($str->countPosixClass('digit'))->toBe(6);
expect($str->countPosixClass('punct'))->toBe(0);
});
it('filters by POSIX class', function () {
$str = PosixString::fromString('abc123xyz');
$alphaOnly = $str->filterByPosixClass('alpha');
$digitOnly = $str->filterByPosixClass('digit');
expect($alphaOnly->value)->toBe('abcxyz');
expect($digitOnly->value)->toBe('123');
});
});
describe('Conversion Methods', function () {
it('converts to TypedString', function () {
$posix = PosixString::fromString('test');
$typed = $posix->toTypedString();
expect($typed->value)->toBe('test');
expect($typed->isAlphabetic())->toBeTrue();
});
it('converts to string', function () {
$str = PosixString::fromString('hello');
expect($str->toString())->toBe('hello');
expect((string) $str)->toBe('hello');
});
});
describe('Comparison Methods', function () {
it('checks equality', function () {
$str1 = PosixString::fromString('test');
$str2 = PosixString::fromString('test');
$str3 = PosixString::fromString('other');
expect($str1->equals($str2))->toBeTrue();
expect($str1->equals($str3))->toBeFalse();
});
it('checks if empty', function () {
$empty = PosixString::fromString('');
$notEmpty = PosixString::fromString('test');
expect($empty->isEmpty())->toBeTrue();
expect($notEmpty->isEmpty())->toBeFalse();
});
it('checks if not empty', function () {
$empty = PosixString::fromString('');
$notEmpty = PosixString::fromString('test');
expect($empty->isNotEmpty())->toBeFalse();
expect($notEmpty->isNotEmpty())->toBeTrue();
});
});
describe('Edge Cases', function () {
it('handles empty string validation', function () {
$empty = PosixString::fromString('');
expect($empty->isAlpha())->toBeFalse();
expect($empty->isAlnum())->toBeFalse();
expect($empty->isDigit())->toBeFalse();
});
it('handles single character validation', function () {
$alpha = PosixString::fromString('a');
$digit = PosixString::fromString('5');
$punct = PosixString::fromString('!');
expect($alpha->isAlpha())->toBeTrue();
expect($digit->isDigit())->toBeTrue();
expect($punct->isPunct())->toBeTrue();
});
it('handles mixed character sets', function () {
$mixed = PosixString::fromString('abc123!@#');
expect($mixed->isAlnum())->toBeFalse(); // Contains punctuation
expect($mixed->containsPosixClass('alpha'))->toBeTrue();
expect($mixed->containsPosixClass('digit'))->toBeTrue();
expect($mixed->containsPosixClass('punct'))->toBeTrue();
});
});
describe('Real World Use Cases', function () {
it('validates usernames', function () {
$validUsername = PosixString::fromString('john_doe_123');
$invalidUsername = PosixString::fromString('john-doe@example');
expect($validUsername->isWord())->toBeTrue();
expect($invalidUsername->isWord())->toBeFalse();
});
it('validates identifiers', function () {
$validId = PosixString::fromString('MAX_TIMEOUT_VALUE');
$invalidId = PosixString::fromString('max-timeout-value');
expect($validId->isWord())->toBeTrue();
expect($invalidId->isWord())->toBeFalse();
});
it('validates hexadecimal color codes', function () {
$validColor = PosixString::fromString('FF5733');
$invalidColor = PosixString::fromString('GG5733');
expect($validColor->isXdigit())->toBeTrue();
expect($invalidColor->isXdigit())->toBeFalse();
});
it('validates numeric strings', function () {
$validNum = PosixString::fromString('123456');
$invalidNum = PosixString::fromString('12.34');
expect($validNum->isDigit())->toBeTrue();
expect($invalidNum->isDigit())->toBeFalse();
});
});
});

View File

@@ -0,0 +1,296 @@
<?php
declare(strict_types=1);
use App\Framework\String\ValueObjects\PosixWordString;
use App\Framework\String\ValueObjects\TypedString;
use App\Framework\String\ValueObjects\PosixString;
describe('PosixWordString', function () {
describe('Validation', function () {
it('accepts valid word strings', function () {
$word1 = PosixWordString::fromString('hello');
$word2 = PosixWordString::fromString('Hello123');
$word3 = PosixWordString::fromString('user_name');
$word4 = PosixWordString::fromString('MAX_VALUE');
$word5 = PosixWordString::fromString('_private');
expect($word1->value)->toBe('hello');
expect($word2->value)->toBe('Hello123');
expect($word3->value)->toBe('user_name');
expect($word4->value)->toBe('MAX_VALUE');
expect($word5->value)->toBe('_private');
});
it('rejects strings with hyphens', function () {
PosixWordString::fromString('user-name');
})->throws(\InvalidArgumentException::class, 'String must contain only POSIX word characters');
it('rejects strings with spaces', function () {
PosixWordString::fromString('hello world');
})->throws(\InvalidArgumentException::class, 'String must contain only POSIX word characters');
it('rejects strings with special characters', function () {
PosixWordString::fromString('hello@world');
})->throws(\InvalidArgumentException::class, 'String must contain only POSIX word characters');
it('rejects strings with punctuation', function () {
PosixWordString::fromString('hello.world');
})->throws(\InvalidArgumentException::class, 'String must contain only POSIX word characters');
it('rejects strings with operators', function () {
PosixWordString::fromString('a+b');
})->throws(\InvalidArgumentException::class, 'String must contain only POSIX word characters');
it('rejects empty strings', function () {
PosixWordString::fromString('');
})->throws(\InvalidArgumentException::class, 'String must not be empty');
it('rejects strings with parentheses', function () {
PosixWordString::fromString('function()');
})->throws(\InvalidArgumentException::class, 'String must contain only POSIX word characters');
it('rejects strings with brackets', function () {
PosixWordString::fromString('array[0]');
})->throws(\InvalidArgumentException::class, 'String must contain only POSIX word characters');
});
describe('Conversion Methods', function () {
it('converts to TypedString', function () {
$word = PosixWordString::fromString('user_id');
$typed = $word->toTypedString();
expect($typed)->toBeInstanceOf(TypedString::class);
expect($typed->value)->toBe('user_id');
expect($typed->isAlphanumeric())->toBeFalse(); // Contains underscore
});
it('converts to PosixString', function () {
$word = PosixWordString::fromString('MAX_VALUE');
$posix = $word->toPosixString();
expect($posix)->toBeInstanceOf(PosixString::class);
expect($posix->value)->toBe('MAX_VALUE');
expect($posix->isWord())->toBeTrue();
});
it('converts to string', function () {
$word = PosixWordString::fromString('username');
expect($word->toString())->toBe('username');
expect((string) $word)->toBe('username');
});
});
describe('Comparison Methods', function () {
it('checks equality', function () {
$word1 = PosixWordString::fromString('user_id');
$word2 = PosixWordString::fromString('user_id');
$word3 = PosixWordString::fromString('user_name');
expect($word1->equals($word2))->toBeTrue();
expect($word1->equals($word3))->toBeFalse();
});
it('compares case-sensitive', function () {
$lower = PosixWordString::fromString('username');
$upper = PosixWordString::fromString('USERNAME');
expect($lower->equals($upper))->toBeFalse();
});
});
describe('Real World Use Cases', function () {
describe('PHP Identifiers', function () {
it('validates variable names', function () {
$var1 = PosixWordString::fromString('userName');
$var2 = PosixWordString::fromString('user_id');
$var3 = PosixWordString::fromString('_privateVar');
expect($var1->value)->toBe('userName');
expect($var2->value)->toBe('user_id');
expect($var3->value)->toBe('_privateVar');
});
it('validates constant names', function () {
$const1 = PosixWordString::fromString('MAX_SIZE');
$const2 = PosixWordString::fromString('API_KEY');
$const3 = PosixWordString::fromString('DEFAULT_TIMEOUT');
expect($const1->value)->toBe('MAX_SIZE');
expect($const2->value)->toBe('API_KEY');
expect($const3->value)->toBe('DEFAULT_TIMEOUT');
});
it('validates function names', function () {
$func1 = PosixWordString::fromString('calculateTotal');
$func2 = PosixWordString::fromString('get_user_by_id');
$func3 = PosixWordString::fromString('__construct');
expect($func1->value)->toBe('calculateTotal');
expect($func2->value)->toBe('get_user_by_id');
expect($func3->value)->toBe('__construct');
});
it('rejects invalid PHP identifiers', function () {
PosixWordString::fromString('my-function');
})->throws(\InvalidArgumentException::class);
});
describe('Database Column Names', function () {
it('validates snake_case column names', function () {
$col1 = PosixWordString::fromString('user_id');
$col2 = PosixWordString::fromString('created_at');
$col3 = PosixWordString::fromString('email_verified_at');
expect($col1->value)->toBe('user_id');
expect($col2->value)->toBe('created_at');
expect($col3->value)->toBe('email_verified_at');
});
it('rejects kebab-case column names', function () {
PosixWordString::fromString('user-id');
})->throws(\InvalidArgumentException::class);
});
describe('Usernames', function () {
it('validates valid usernames', function () {
$user1 = PosixWordString::fromString('john_doe');
$user2 = PosixWordString::fromString('user123');
$user3 = PosixWordString::fromString('admin_user');
expect($user1->value)->toBe('john_doe');
expect($user2->value)->toBe('user123');
expect($user3->value)->toBe('admin_user');
});
it('rejects usernames with special characters', function () {
PosixWordString::fromString('john@doe');
})->throws(\InvalidArgumentException::class);
it('rejects usernames with hyphens', function () {
PosixWordString::fromString('john-doe');
})->throws(\InvalidArgumentException::class);
it('rejects usernames with spaces', function () {
PosixWordString::fromString('john doe');
})->throws(\InvalidArgumentException::class);
});
describe('URL Slugs (with underscore)', function () {
it('validates underscore slugs', function () {
$slug1 = PosixWordString::fromString('my_blog_post');
$slug2 = PosixWordString::fromString('product_category_1');
$slug3 = PosixWordString::fromString('about_us');
expect($slug1->value)->toBe('my_blog_post');
expect($slug2->value)->toBe('product_category_1');
expect($slug3->value)->toBe('about_us');
});
it('rejects hyphenated slugs', function () {
// Note: Hyphenated slugs need different validation
PosixWordString::fromString('my-blog-post');
})->throws(\InvalidArgumentException::class);
});
});
describe('Single Character Strings', function () {
it('accepts single letter', function () {
$a = PosixWordString::fromString('a');
$Z = PosixWordString::fromString('Z');
expect($a->value)->toBe('a');
expect($Z->value)->toBe('Z');
});
it('accepts single digit', function () {
$five = PosixWordString::fromString('5');
expect($five->value)->toBe('5');
});
it('accepts single underscore', function () {
$underscore = PosixWordString::fromString('_');
expect($underscore->value)->toBe('_');
});
it('rejects single special character', function () {
PosixWordString::fromString('!');
})->throws(\InvalidArgumentException::class);
it('rejects single hyphen', function () {
PosixWordString::fromString('-');
})->throws(\InvalidArgumentException::class);
});
describe('Edge Cases', function () {
it('handles very long word strings', function () {
$longString = str_repeat('abc123_', 100);
$word = PosixWordString::fromString($longString);
expect($word->value)->toBe($longString);
expect(strlen($word->value))->toBe(700);
});
it('handles mixed case correctly', function () {
$mixed = PosixWordString::fromString('CamelCase_With_Underscore123');
expect($mixed->value)->toBe('CamelCase_With_Underscore123');
});
it('accepts strings starting with underscore', function () {
$leading = PosixWordString::fromString('_privateMethod');
expect($leading->value)->toBe('_privateMethod');
});
it('accepts strings starting with digit', function () {
// Note: Valid for word class, but not valid PHP identifiers
$digit = PosixWordString::fromString('123abc');
expect($digit->value)->toBe('123abc');
});
it('accepts strings with only digits', function () {
$digits = PosixWordString::fromString('123456');
expect($digits->value)->toBe('123456');
});
it('accepts strings with only underscores', function () {
$underscores = PosixWordString::fromString('___');
expect($underscores->value)->toBe('___');
});
});
describe('Boundary Cases', function () {
it('handles maximum practical length', function () {
// 255 characters - typical database VARCHAR limit
$longIdentifier = str_repeat('a', 255);
$word = PosixWordString::fromString($longIdentifier);
expect(strlen($word->value))->toBe(255);
});
it('handles alphanumeric only', function () {
$alnum = PosixWordString::fromString('abc123XYZ789');
expect($alnum->value)->toBe('abc123XYZ789');
});
it('handles underscores only', function () {
$underscores = PosixWordString::fromString('_____');
expect($underscores->value)->toBe('_____');
});
it('handles mixed content', function () {
$mixed = PosixWordString::fromString('a1_B2_c3_D4');
expect($mixed->value)->toBe('a1_B2_c3_D4');
});
});
});

View File

@@ -0,0 +1,170 @@
<?php
declare(strict_types=1);
use App\Framework\String\ValueObjects\{
AlphanumericString,
NumericString,
HexadecimalString,
PrintableString
};
describe('AlphanumericString', function () {
it('creates from valid alphanumeric string', function () {
$str = AlphanumericString::fromString('abc123');
expect($str->value)->toBe('abc123');
});
it('rejects non-alphanumeric strings', function () {
AlphanumericString::fromString('abc-123');
})->throws(InvalidArgumentException::class, 'alphanumeric');
it('rejects empty strings', function () {
AlphanumericString::fromString('');
})->throws(InvalidArgumentException::class);
it('converts to TypedString', function () {
$str = AlphanumericString::fromString('test123');
$typed = $str->toTypedString();
expect($typed->value)->toBe('test123');
expect($typed->isAlphanumeric())->toBeTrue();
});
it('checks equality', function () {
$str1 = AlphanumericString::fromString('abc123');
$str2 = AlphanumericString::fromString('abc123');
$str3 = AlphanumericString::fromString('xyz789');
expect($str1->equals($str2))->toBeTrue();
expect($str1->equals($str3))->toBeFalse();
});
it('converts to string', function () {
$str = AlphanumericString::fromString('test123');
expect((string) $str)->toBe('test123');
});
});
describe('NumericString', function () {
it('creates from valid numeric string', function () {
$str = NumericString::fromString('12345');
expect($str->value)->toBe('12345');
});
it('rejects non-numeric strings', function () {
NumericString::fromString('12.34');
})->throws(InvalidArgumentException::class, 'digits');
it('rejects alphanumeric strings', function () {
NumericString::fromString('123abc');
})->throws(InvalidArgumentException::class);
it('rejects empty strings', function () {
NumericString::fromString('');
})->throws(InvalidArgumentException::class);
it('converts to integer', function () {
$str = NumericString::fromString('12345');
expect($str->toInt())->toBe(12345);
});
it('converts to float', function () {
$str = NumericString::fromString('12345');
expect($str->toFloat())->toBe(12345.0);
});
it('handles large numbers', function () {
$str = NumericString::fromString('999999999');
expect($str->toInt())->toBe(999999999);
});
});
describe('HexadecimalString', function () {
it('creates from valid hexadecimal string', function () {
$str = HexadecimalString::fromString('deadbeef');
expect($str->value)->toBe('deadbeef');
});
it('accepts uppercase hexadecimal', function () {
$str = HexadecimalString::fromString('DEADBEEF');
expect($str->value)->toBe('DEADBEEF');
});
it('accepts mixed case hexadecimal', function () {
$str = HexadecimalString::fromString('DeAdBeEf');
expect($str->value)->toBe('DeAdBeEf');
});
it('rejects non-hexadecimal strings', function () {
HexadecimalString::fromString('ghijk');
})->throws(InvalidArgumentException::class, 'hexadecimal');
it('rejects empty strings', function () {
HexadecimalString::fromString('');
})->throws(InvalidArgumentException::class);
it('converts to binary', function () {
$str = HexadecimalString::fromString('48656c6c6f'); // "Hello"
$binary = $str->toBinary();
expect($binary)->toBe('Hello');
});
it('converts to integer', function () {
$str = HexadecimalString::fromString('ff');
expect($str->toInt())->toBe(255);
});
it('handles long hexadecimal strings', function () {
$str = HexadecimalString::fromString('0123456789abcdef');
expect($str->toInt())->toBe(81985529216486895);
});
});
describe('PrintableString', function () {
it('creates from valid printable string', function () {
$str = PrintableString::fromString('Hello World!');
expect($str->value)->toBe('Hello World!');
});
it('accepts strings with whitespace', function () {
$str = PrintableString::fromString("Hello\nWorld\tTest");
expect($str->value)->toContain('Hello');
});
it('rejects strings with control characters', function () {
PrintableString::fromString("Hello\x00World");
})->throws(InvalidArgumentException::class, 'printable');
it('rejects empty strings', function () {
PrintableString::fromString('');
})->throws(InvalidArgumentException::class);
it('accepts special characters', function () {
$str = PrintableString::fromString('!@#$%^&*()_+-=[]{}|;:,.<>?');
expect($str->value)->toContain('!@#$');
});
it('converts to TypedString', function () {
$str = PrintableString::fromString('Test String');
$typed = $str->toTypedString();
expect($typed->value)->toBe('Test String');
expect($typed->isPrintable())->toBeTrue();
});
});

View File

@@ -0,0 +1,170 @@
<?php
declare(strict_types=1);
use App\Framework\String\ValueObjects\TypedString;
describe('TypedString - Character Type Checks', function () {
it('validates alphanumeric strings', function () {
expect(TypedString::fromString('abc123')->isAlphanumeric())->toBeTrue();
expect(TypedString::fromString('ABC123')->isAlphanumeric())->toBeTrue();
expect(TypedString::fromString('abc-123')->isAlphanumeric())->toBeFalse();
expect(TypedString::fromString('')->isAlphanumeric())->toBeFalse();
});
it('validates alphabetic strings', function () {
expect(TypedString::fromString('abcABC')->isAlphabetic())->toBeTrue();
expect(TypedString::fromString('abc123')->isAlphabetic())->toBeFalse();
expect(TypedString::fromString('')->isAlphabetic())->toBeFalse();
});
it('validates digit strings', function () {
expect(TypedString::fromString('123456')->isDigits())->toBeTrue();
expect(TypedString::fromString('12.34')->isDigits())->toBeFalse();
expect(TypedString::fromString('abc')->isDigits())->toBeFalse();
expect(TypedString::fromString('')->isDigits())->toBeFalse();
});
it('validates lowercase strings', function () {
expect(TypedString::fromString('abc')->isLowercase())->toBeTrue();
expect(TypedString::fromString('Abc')->isLowercase())->toBeFalse();
expect(TypedString::fromString('')->isLowercase())->toBeFalse();
});
it('validates uppercase strings', function () {
expect(TypedString::fromString('ABC')->isUppercase())->toBeTrue();
expect(TypedString::fromString('Abc')->isUppercase())->toBeFalse();
expect(TypedString::fromString('')->isUppercase())->toBeFalse();
});
it('validates hexadecimal strings', function () {
expect(TypedString::fromString('deadbeef')->isHexadecimal())->toBeTrue();
expect(TypedString::fromString('DEADBEEF')->isHexadecimal())->toBeTrue();
expect(TypedString::fromString('0123456789abcdef')->isHexadecimal())->toBeTrue();
expect(TypedString::fromString('ghijk')->isHexadecimal())->toBeFalse();
expect(TypedString::fromString('')->isHexadecimal())->toBeFalse();
});
it('validates whitespace strings', function () {
expect(TypedString::fromString(' ')->isWhitespace())->toBeTrue();
expect(TypedString::fromString("\t\n")->isWhitespace())->toBeTrue();
expect(TypedString::fromString('abc')->isWhitespace())->toBeFalse();
expect(TypedString::fromString('')->isWhitespace())->toBeFalse();
});
it('validates printable strings', function () {
expect(TypedString::fromString('Hello World')->isPrintable())->toBeTrue();
expect(TypedString::fromString("Hello\x00World")->isPrintable())->toBeFalse();
expect(TypedString::fromString('')->isPrintable())->toBeFalse();
});
it('validates visible strings', function () {
expect(TypedString::fromString('abc123')->isVisible())->toBeTrue();
expect(TypedString::fromString('abc 123')->isVisible())->toBeFalse();
expect(TypedString::fromString('')->isVisible())->toBeFalse();
});
});
describe('TypedString - Length Checks', function () {
it('checks if string is empty', function () {
expect(TypedString::fromString('')->isEmpty())->toBeTrue();
expect(TypedString::fromString('abc')->isEmpty())->toBeFalse();
});
it('checks if string is not empty', function () {
expect(TypedString::fromString('abc')->isNotEmpty())->toBeTrue();
expect(TypedString::fromString('')->isNotEmpty())->toBeFalse();
});
it('returns string length', function () {
expect(TypedString::fromString('abc')->length())->toBe(3);
expect(TypedString::fromString('')->length())->toBe(0);
expect(TypedString::fromString('hello world')->length())->toBe(11);
});
it('checks minimum length', function () {
$str = TypedString::fromString('hello');
expect($str->hasMinLength(3))->toBeTrue();
expect($str->hasMinLength(5))->toBeTrue();
expect($str->hasMinLength(6))->toBeFalse();
});
it('checks maximum length', function () {
$str = TypedString::fromString('hello');
expect($str->hasMaxLength(10))->toBeTrue();
expect($str->hasMaxLength(5))->toBeTrue();
expect($str->hasMaxLength(4))->toBeFalse();
});
it('checks length range', function () {
$str = TypedString::fromString('hello');
expect($str->hasLengthBetween(3, 10))->toBeTrue();
expect($str->hasLengthBetween(5, 5))->toBeTrue();
expect($str->hasLengthBetween(6, 10))->toBeFalse();
expect($str->hasLengthBetween(1, 4))->toBeFalse();
});
it('checks exact length', function () {
$str = TypedString::fromString('hello');
expect($str->hasExactLength(5))->toBeTrue();
expect($str->hasExactLength(4))->toBeFalse();
expect($str->hasExactLength(6))->toBeFalse();
});
});
describe('TypedString - Pattern Matching', function () {
it('matches regular expressions', function () {
$str = TypedString::fromString('hello123');
expect($str->matches('/^hello/'))->toBeTrue();
expect($str->matches('/\d+$/'))->toBeTrue();
expect($str->matches('/^goodbye/'))->toBeFalse();
});
it('checks if starts with prefix', function () {
$str = TypedString::fromString('hello world');
expect($str->startsWith('hello'))->toBeTrue();
expect($str->startsWith('h'))->toBeTrue();
expect($str->startsWith('world'))->toBeFalse();
});
it('checks if ends with suffix', function () {
$str = TypedString::fromString('hello world');
expect($str->endsWith('world'))->toBeTrue();
expect($str->endsWith('d'))->toBeTrue();
expect($str->endsWith('hello'))->toBeFalse();
});
it('checks if contains substring', function () {
$str = TypedString::fromString('hello world');
expect($str->contains('hello'))->toBeTrue();
expect($str->contains('world'))->toBeTrue();
expect($str->contains('lo wo'))->toBeTrue();
expect($str->contains('goodbye'))->toBeFalse();
});
});
describe('TypedString - Comparison & Conversion', function () {
it('checks equality', function () {
$str1 = TypedString::fromString('hello');
$str2 = TypedString::fromString('hello');
$str3 = TypedString::fromString('world');
expect($str1->equals($str2))->toBeTrue();
expect($str1->equals($str3))->toBeFalse();
});
it('converts to string', function () {
$str = TypedString::fromString('hello');
expect($str->toString())->toBe('hello');
expect((string) $str)->toBe('hello');
});
});

View File

@@ -0,0 +1,440 @@
<?php
declare(strict_types=1);
use App\Framework\String\ValueObjects\TypedString;
describe('TypedStringValidator - Fluent API', function () {
it('validates alphanumeric with fluent interface', function () {
$result = TypedString::fromString('abc123')
->validate()
->alphanumeric()
->orNull();
expect($result)->not->toBeNull();
expect($result->value)->toBe('abc123');
});
it('rejects non-alphanumeric strings', function () {
$result = TypedString::fromString('abc-123')
->validate()
->alphanumeric()
->orNull();
expect($result)->toBeNull();
});
it('validates length constraints', function () {
$result = TypedString::fromString('hello')
->validate()
->minLength(3)
->maxLength(10)
->orNull();
expect($result)->not->toBeNull();
});
it('rejects strings below minimum length', function () {
$result = TypedString::fromString('ab')
->validate()
->minLength(5)
->orNull();
expect($result)->toBeNull();
});
it('rejects strings above maximum length', function () {
$result = TypedString::fromString('hello world')
->validate()
->maxLength(5)
->orNull();
expect($result)->toBeNull();
});
});
describe('TypedStringValidator - Complex Chains', function () {
it('validates complex username format', function () {
$username = TypedString::fromString('user123')
->validate()
->alphanumeric()
->minLength(3)
->maxLength(20)
->orThrow('Invalid username');
expect($username->value)->toBe('user123');
});
it('validates password with custom rules', function () {
$password = TypedString::fromString('Pass123!')
->validate()
->minLength(8)
->custom(
fn($ts) => preg_match('/[A-Z]/', $ts->value) === 1,
'Must contain uppercase letter'
)
->custom(
fn($ts) => preg_match('/[0-9]/', $ts->value) === 1,
'Must contain digit'
)
->orThrow();
expect($password->value)->toBe('Pass123!');
});
it('throws on invalid password', function () {
TypedString::fromString('weak')
->validate()
->minLength(8)
->orThrow('Password too weak');
})->throws(InvalidArgumentException::class, 'Password too weak');
});
describe('TypedStringValidator - Error Handling', function () {
it('collects multiple validation errors', function () {
$validator = TypedString::fromString('ab')
->validate()
->alphanumeric()
->minLength(5);
expect($validator->fails())->toBeTrue();
$errors = $validator->getErrors();
expect($errors)->toHaveCount(1);
expect($errors[0])->toContain('at least 5 characters');
});
it('returns first error message', function () {
$validator = TypedString::fromString('ab')
->validate()
->minLength(5)
->maxLength(3);
$firstError = $validator->getFirstError();
expect($firstError)->toContain('at least 5 characters');
});
it('returns custom error messages', function () {
$validator = TypedString::fromString('abc-123')
->validate()
->alphanumeric('Username must be alphanumeric');
expect($validator->fails())->toBeTrue();
expect($validator->getFirstError())->toBe('Username must be alphanumeric');
});
});
describe('TypedStringValidator - Pattern Validators', function () {
it('validates regex patterns', function () {
$email = TypedString::fromString('test@example.com')
->validate()
->matches('/^[\w\.\-]+@[\w\.\-]+\.\w+$/', 'Invalid email format')
->orThrow();
expect($email->value)->toBe('test@example.com');
});
it('validates start with prefix', function () {
$result = TypedString::fromString('user_123')
->validate()
->startsWith('user_')
->orNull();
expect($result)->not->toBeNull();
});
it('validates end with suffix', function () {
$result = TypedString::fromString('file.txt')
->validate()
->endsWith('.txt')
->orNull();
expect($result)->not->toBeNull();
});
it('validates contains substring', function () {
$result = TypedString::fromString('hello_world')
->validate()
->contains('_')
->orNull();
expect($result)->not->toBeNull();
});
it('validates does not contain substring', function () {
$result = TypedString::fromString('helloworld')
->validate()
->doesNotContain(' ')
->orNull();
expect($result)->not->toBeNull();
});
});
describe('TypedStringValidator - Termination Methods', function () {
it('throws exception on validation failure', function () {
TypedString::fromString('invalid')
->validate()
->digits()
->orThrow('Must be numeric');
})->throws(InvalidArgumentException::class, 'Must be numeric');
it('returns null on validation failure', function () {
$result = TypedString::fromString('invalid')
->validate()
->digits()
->orNull();
expect($result)->toBeNull();
});
it('returns default value on validation failure', function () {
$result = TypedString::fromString('invalid')
->validate()
->digits()
->orDefault('123');
expect($result->value)->toBe('123');
});
it('passes validation and returns original value', function () {
$result = TypedString::fromString('valid123')
->validate()
->alphanumeric()
->orDefault('fallback');
expect($result->value)->toBe('valid123');
});
});
describe('TypedStringValidator - POSIX Validators', function () {
describe('posixAlnum', function () {
it('validates alphanumeric POSIX strings', function () {
$result = TypedString::fromString('abc123XYZ')
->validate()
->posixAlnum()
->orThrow();
expect($result->value)->toBe('abc123XYZ');
});
it('rejects strings with non-alphanumeric characters', function () {
TypedString::fromString('abc-123')
->validate()
->posixAlnum()
->orThrow();
})->throws(\InvalidArgumentException::class);
it('works in validation chain', function () {
$result = TypedString::fromString('test123')
->validate()
->notEmpty()
->posixAlnum()
->minLength(5)
->orThrow();
expect($result->value)->toBe('test123');
});
});
describe('posixAlpha', function () {
it('validates alphabetic POSIX strings', function () {
$result = TypedString::fromString('abcXYZ')
->validate()
->posixAlpha()
->orThrow();
expect($result->value)->toBe('abcXYZ');
});
it('rejects strings with digits', function () {
TypedString::fromString('abc123')
->validate()
->posixAlpha()
->orThrow();
})->throws(\InvalidArgumentException::class);
it('rejects strings with special characters', function () {
TypedString::fromString('hello-world')
->validate()
->posixAlpha()
->orThrow();
})->throws(\InvalidArgumentException::class);
});
describe('posixPunct', function () {
it('validates punctuation POSIX strings', function () {
$result = TypedString::fromString('!@#$%^&*()')
->validate()
->posixPunct()
->orThrow();
expect($result->value)->toBe('!@#$%^&*()');
});
it('rejects strings with alphanumeric characters', function () {
TypedString::fromString('abc!@#')
->validate()
->posixPunct()
->orThrow();
})->throws(\InvalidArgumentException::class);
});
describe('posixWord', function () {
it('validates word POSIX strings (alphanumeric + underscore)', function () {
$result = TypedString::fromString('user_name_123')
->validate()
->posixWord()
->orThrow();
expect($result->value)->toBe('user_name_123');
});
it('rejects strings with hyphens', function () {
TypedString::fromString('user-name')
->validate()
->posixWord()
->orThrow();
})->throws(\InvalidArgumentException::class);
it('validates PHP identifiers', function () {
$result = TypedString::fromString('_privateVar')
->validate()
->posixWord()
->orThrow();
expect($result->value)->toBe('_privateVar');
});
});
describe('posixLower', function () {
it('validates lowercase POSIX strings', function () {
$result = TypedString::fromString('hello')
->validate()
->posixLower()
->orThrow();
expect($result->value)->toBe('hello');
});
it('rejects strings with uppercase characters', function () {
TypedString::fromString('Hello')
->validate()
->posixLower()
->orThrow();
})->throws(\InvalidArgumentException::class);
it('rejects strings with digits', function () {
TypedString::fromString('hello123')
->validate()
->posixLower()
->orThrow();
})->throws(\InvalidArgumentException::class);
});
describe('posixUpper', function () {
it('validates uppercase POSIX strings', function () {
$result = TypedString::fromString('HELLO')
->validate()
->posixUpper()
->orThrow();
expect($result->value)->toBe('HELLO');
});
it('rejects strings with lowercase characters', function () {
TypedString::fromString('Hello')
->validate()
->posixUpper()
->orThrow();
})->throws(\InvalidArgumentException::class);
it('validates constant naming', function () {
$result = TypedString::fromString('MAX_VALUE')
->validate()
->posixUpper()
->orThrow();
expect($result->value)->toBe('MAX_VALUE');
});
});
describe('posixPrint', function () {
it('validates printable POSIX strings (includes space)', function () {
$result = TypedString::fromString('Hello World!')
->validate()
->posixPrint()
->orThrow();
expect($result->value)->toBe('Hello World!');
});
it('rejects strings with control characters', function () {
TypedString::fromString("Hello\x00World")
->validate()
->posixPrint()
->orThrow();
})->throws(\InvalidArgumentException::class);
});
describe('posixGraph', function () {
it('validates visible POSIX strings (excludes space)', function () {
$result = TypedString::fromString('Hello!')
->validate()
->posixGraph()
->orThrow();
expect($result->value)->toBe('Hello!');
});
it('rejects strings with spaces', function () {
TypedString::fromString('Hello World')
->validate()
->posixGraph()
->orThrow();
})->throws(\InvalidArgumentException::class);
it('validates URLs without spaces', function () {
$result = TypedString::fromString('https://example.com')
->validate()
->posixGraph()
->orThrow();
expect($result->value)->toBe('https://example.com');
});
});
describe('Complex POSIX Validation Chains', function () {
it('combines POSIX validators with length checks', function () {
$result = TypedString::fromString('user123')
->validate()
->posixAlnum()
->minLength(5)
->maxLength(20)
->orThrow();
expect($result->value)->toBe('user123');
});
it('combines POSIX with pattern matching', function () {
$result = TypedString::fromString('test_value')
->validate()
->posixWord()
->matches('/^[a-z_]+$/')
->orThrow();
expect($result->value)->toBe('test_value');
});
it('uses POSIX validators in complex validation scenarios', function () {
$result = TypedString::fromString('AdminUser')
->validate()
->notEmpty()
->posixAlpha()
->minLength(5)
->orDefault('Guest');
expect($result->value)->toBe('AdminUser');
});
});
});