163 lines
3.9 KiB
PHP
163 lines
3.9 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace Tez\Utils\Tests\Validate;
|
|
|
|
use PHPUnit\Framework\TestCase;
|
|
use Tez\Utils\Validate\Composite;
|
|
use Tez\Utils\Validate\Rules;
|
|
|
|
final class CompositeTest extends TestCase
|
|
{
|
|
// --- all ---
|
|
|
|
public function testAllReturnsTrueWhenEveryRulePasses(): void
|
|
{
|
|
$check = Composite::all(
|
|
fn($v) => is_string($v) && Rules::minLength($v, 3),
|
|
fn($v) => is_string($v) && Rules::maxLength($v, 10),
|
|
fn($v) => is_string($v) && Rules::matches($v, '/^[a-z]+$/'),
|
|
);
|
|
|
|
self::assertTrue($check('hello'));
|
|
}
|
|
|
|
public function testAllReturnsFalseWhenAnyRuleFails(): void
|
|
{
|
|
$check = Composite::all(
|
|
fn($v) => is_string($v) && Rules::minLength($v, 3),
|
|
fn($v) => is_string($v) && Rules::maxLength($v, 4),
|
|
);
|
|
|
|
self::assertFalse($check('toolong'));
|
|
}
|
|
|
|
public function testAllShortCircuitsOnFirstFailure(): void
|
|
{
|
|
$called = 0;
|
|
$check = Composite::all(
|
|
fn() => false,
|
|
function () use (&$called) {
|
|
$called++;
|
|
|
|
return true;
|
|
},
|
|
);
|
|
|
|
$check('x');
|
|
|
|
self::assertSame(0, $called);
|
|
}
|
|
|
|
public function testAllWithNoRulesReturnsTrue(): void
|
|
{
|
|
$check = Composite::all();
|
|
self::assertTrue($check('anything'));
|
|
}
|
|
|
|
// --- any ---
|
|
|
|
public function testAnyReturnsTrueWhenOneRulePasses(): void
|
|
{
|
|
$check = Composite::any(
|
|
fn($v) => is_string($v) && Rules::isEmail($v),
|
|
fn($v) => is_string($v) && Rules::isUuid($v),
|
|
);
|
|
|
|
self::assertTrue($check('user@example.com'));
|
|
}
|
|
|
|
public function testAnyReturnsFalseWhenNoRulePasses(): void
|
|
{
|
|
$check = Composite::any(
|
|
fn($v) => is_string($v) && Rules::isEmail($v),
|
|
fn($v) => is_string($v) && Rules::isUuid($v),
|
|
);
|
|
|
|
self::assertFalse($check('neither'));
|
|
}
|
|
|
|
public function testAnyShortCircuitsOnFirstSuccess(): void
|
|
{
|
|
$called = 0;
|
|
$check = Composite::any(
|
|
fn() => true,
|
|
function () use (&$called) {
|
|
$called++;
|
|
|
|
return false;
|
|
},
|
|
);
|
|
|
|
$check('x');
|
|
|
|
self::assertSame(0, $called);
|
|
}
|
|
|
|
public function testAnyWithNoRulesReturnsFalse(): void
|
|
{
|
|
$check = Composite::any();
|
|
self::assertFalse($check('anything'));
|
|
}
|
|
|
|
// --- none ---
|
|
|
|
public function testNoneReturnsTrueWhenNoRuleMatches(): void
|
|
{
|
|
$isReserved = Composite::none(
|
|
fn($v) => $v === 'admin',
|
|
fn($v) => $v === 'root',
|
|
);
|
|
|
|
self::assertTrue($isReserved('alice'));
|
|
}
|
|
|
|
public function testNoneReturnsFalseWhenAnyRuleMatches(): void
|
|
{
|
|
$isReserved = Composite::none(
|
|
fn($v) => $v === 'admin',
|
|
fn($v) => $v === 'root',
|
|
);
|
|
|
|
self::assertFalse($isReserved('admin'));
|
|
}
|
|
|
|
public function testNoneShortCircuitsOnFirstMatch(): void
|
|
{
|
|
$called = 0;
|
|
$check = Composite::none(
|
|
fn() => true,
|
|
function () use (&$called) {
|
|
$called++;
|
|
|
|
return false;
|
|
},
|
|
);
|
|
|
|
$check('x');
|
|
|
|
self::assertSame(0, $called);
|
|
}
|
|
|
|
public function testNoneWithNoRulesReturnsTrue(): void
|
|
{
|
|
$check = Composite::none();
|
|
self::assertTrue($check('anything'));
|
|
}
|
|
|
|
// --- integration with Guard ---
|
|
|
|
public function testAllComposesWithGuard(): void
|
|
{
|
|
$isStrongPassword = Composite::all(
|
|
fn($v) => is_string($v) && Rules::minLength($v, 8),
|
|
fn($v) => is_string($v) && Rules::matches($v, '/[A-Z]/'),
|
|
fn($v) => is_string($v) && Rules::matches($v, '/[0-9]/'),
|
|
);
|
|
|
|
self::assertTrue($isStrongPassword('Secret42!'));
|
|
self::assertFalse($isStrongPassword('weak'));
|
|
}
|
|
}
|