PHP Classes

File: tests/run-tests.php

Recommend this page to a friend!
  Packages of AKONO Metsam Nathan   Contextual Validator - Cross-Field Coherence Checker   tests/run-tests.php   Download  
File: tests/run-tests.php
Role: Example script
Content type: text/plain
Description: Dependency-free automated test suite (27 assertions) covering every rule.
Class: Contextual Validator - Cross-Field Coherence Checker
Validate multiple form fields that are related
Author: By
Last change:
Date: 11 days ago
Size: 7,155 bytes
 

Contents

Class file image Download
<?php

declare(strict_types=1);

require
__DIR__ . '/../src/ContextualValidator.php';

use
ContextValidate\ContextualValidator;

$failures = 0;
$passed = 0;

/**
 * @param mixed $expected
 * @param mixed $actual
 */
function assertSame_($expected, $actual, string $label): void
{
    global
$failures, $passed;
    if (
$expected === $actual) {
       
$passed++;
        echo
" [OK] {$label}\n";
    } else {
       
$failures++;
        echo
" [FAIL] {$label}\n";
        echo
' expected: ' . var_export($expected, true) . "\n";
        echo
' actual: ' . var_export($actual, true) . "\n";
    }
}

function
assertTrue_(bool $condition, string $label): void
{
   
assertSame_(true, $condition, $label);
}

// --- Test 1: postal code / country coherence --------------------------------
echo "Test 1: postal code vs country\n";

$v = new ContextualValidator(['zip' => '90210', 'country' => 'US']);
$v->postalCodeMatchesCountry('zip', 'country');
assertTrue_($v->passes(), 'valid US zip passes');

$v = new ContextualValidator(['zip' => '90210-1234', 'country' => 'FR']);
$v->postalCodeMatchesCountry('zip', 'country');
assertTrue_($v->fails(), 'US zip+4 fails for FR (FR expects exactly 5 digits)');
assertTrue_(isset($v->errors()['zip']), 'error attached to zip field');

$v = new ContextualValidator(['zip' => '75008', 'country' => 'FR']);
$v->postalCodeMatchesCountry('zip', 'country');
assertTrue_($v->passes(), 'valid FR postal code passes');

$v = new ContextualValidator(['zip' => 'anything', 'country' => 'ZZ']);
$v->postalCodeMatchesCountry('zip', 'country');
assertTrue_($v->passes(), 'unknown country is skipped, not flagged invalid');

$v = new ContextualValidator(['zip' => '', 'country' => 'FR']);
$v->postalCodeMatchesCountry('zip', 'country');
assertTrue_($v->passes(), 'empty postal code is skipped (not this rule\'s job)');

// --- Test 2: date range -----------------------------------------------------
echo "\nTest 2: date range\n";

$v = new ContextualValidator(['start_date' => '2026-01-01', 'end_date' => '2026-01-10']);
$v->dateRange('start_date', 'end_date');
assertTrue_($v->passes(), 'start before end passes');

$v = new ContextualValidator(['start_date' => '2026-01-10', 'end_date' => '2026-01-01']);
$v->dateRange('start_date', 'end_date');
assertTrue_($v->fails(), 'start after end fails');

$v = new ContextualValidator(['start_date' => '2026-01-01', 'end_date' => '2026-01-01']);
$v->dateRange('start_date', 'end_date');
assertTrue_($v->passes(), 'equal dates pass by default');

$v = new ContextualValidator(['start_date' => '2026-01-01', 'end_date' => '2026-01-01']);
$v->dateRange('start_date', 'end_date', false);
assertTrue_($v->fails(), 'equal dates fail when allowEqual=false');

// --- Test 3: age vs birthdate ------------------------------------------------
echo "\nTest 3: age vs birthdate\n";

$ref = new \DateTimeImmutable('2026-07-20');

$v = new ContextualValidator(['age' => '36', 'birthdate' => '1990-01-15']);
$v->ageMatchesBirthdate('age', 'birthdate', 0, $ref);
assertTrue_($v->passes(), 'age matches birthdate as of reference date');

$v = new ContextualValidator(['age' => '15', 'birthdate' => '1990-01-15']);
$v->ageMatchesBirthdate('age', 'birthdate', 0, $ref);
assertTrue_($v->fails(), 'mismatched age flagged');

$v = new ContextualValidator(['age' => '37', 'birthdate' => '1990-01-15']);
$v->ageMatchesBirthdate('age', 'birthdate', 1, $ref);
assertTrue_($v->passes(), 'off-by-one tolerated when toleranceYears=1');

// --- Test 4: phone vs country -------------------------------------------------
echo "\nTest 4: phone vs country\n";

$v = new ContextualValidator(['phone' => '212-555-0100', 'country' => 'US']);
$v->phoneMatchesCountry('phone', 'country');
assertTrue_($v->passes(), 'plausible US phone passes');

$v = new ContextualValidator(['phone' => '5550100', 'country' => 'US']);
$v->phoneMatchesCountry('phone', 'country');
assertTrue_($v->fails(), 'too-short number for US fails');

$v = new ContextualValidator(['phone' => '+1 212 555 0100', 'country' => 'US']);
$v->phoneMatchesCountry('phone', 'country');
assertTrue_($v->passes(), 'phone with country calling code prefix still passes');

// --- Test 5: fields match -----------------------------------------------------
echo "\nTest 5: fields match\n";

$v = new ContextualValidator(['password' => 'secret123', 'password_confirmation' => 'secret123']);
$v->fieldsMatch('password', 'password_confirmation');
assertTrue_($v->passes(), 'matching passwords pass');

$v = new ContextualValidator(['password' => 'secret123', 'password_confirmation' => 'different']);
$v->fieldsMatch('password', 'password_confirmation');
assertTrue_($v->fails(), 'mismatched passwords fail');

// --- Test 6: numeric range ----------------------------------------------------
echo "\nTest 6: numeric range\n";

$v = new ContextualValidator(['min_price' => '10', 'max_price' => '50']);
$v->numericRange('min_price', 'max_price');
assertTrue_($v->passes(), 'min <= max passes');

$v = new ContextualValidator(['min_price' => '50', 'max_price' => '10']);
$v->numericRange('min_price', 'max_price');
assertTrue_($v->fails(), 'min > max fails');

// --- Test 7: custom rule -------------------------------------------------------
echo "\nTest 7: custom rule\n";

$v = new ContextualValidator(['discount' => 20, 'price' => 15]);
$v->custom('discount', function (array $data) {
    return
$data['discount'] <= $data['price'] ? true : 'Discount cannot exceed price.';
});
assertTrue_($v->fails(), 'custom rule can fail');

$v = new ContextualValidator(['discount' => 5, 'price' => 15]);
$v->custom('discount', function (array $data) {
    return
$data['discount'] <= $data['price'] ? true : 'Discount cannot exceed price.';
});
assertTrue_($v->passes(), 'custom rule can pass');

// --- Test 8: chaining and multiple errors -------------------------------------
echo "\nTest 8: chaining multiple rules\n";

$v = new ContextualValidator([
   
'zip' => '90210-1234',
   
'country' => 'FR',
   
'start_date' => '2026-05-10',
   
'end_date' => '2026-05-01',
   
'password' => 'a',
   
'password_confirmation' => 'b',
]);
$v->postalCodeMatchesCountry('zip', 'country')
  ->
dateRange('start_date', 'end_date')
  ->
fieldsMatch('password', 'password_confirmation');

assertTrue_($v->fails(), 'multiple broken rules fail overall');
assertSame_(3, count($v->errors()), 'three distinct fields carry errors');
assertSame_(3, count($v->allMessages()), 'three flattened messages');

// --- Test 9: extending postal code patterns -----------------------------------
echo "\nTest 9: custom postal code pattern\n";

ContextualValidator::addPostalCodePattern('CM', '/^\d{5}$/');
$v = new ContextualValidator(['zip' => '12345', 'country' => 'CM']);
$v->postalCodeMatchesCountry('zip', 'country');
assertTrue_($v->passes(), 'custom pattern overrides built-in permissive default');

$v = new ContextualValidator(['zip' => 'abc', 'country' => 'CM']);
$v->postalCodeMatchesCountry('zip', 'country');
assertTrue_($v->fails(), 'custom pattern correctly rejects non-matching value');

echo
"\n---\n{$passed} passed, {$failures} failed\n";
exit(
$failures > 0 ? 1 : 0);