diff --git a/src/Commands/CreateCsv.php b/src/Commands/CreateCsv.php index 0128051f..16ca548d 100644 --- a/src/Commands/CreateCsv.php +++ b/src/Commands/CreateCsv.php @@ -22,6 +22,7 @@ /** * @psalm-suppress PropertyNotSetInConstructor + * @codeCoverageIgnore */ final class CreateCsv extends CliCommand { diff --git a/src/Commands/CreateSchema.php b/src/Commands/CreateSchema.php index feadeafa..0221ff90 100644 --- a/src/Commands/CreateSchema.php +++ b/src/Commands/CreateSchema.php @@ -22,6 +22,7 @@ /** * @psalm-suppress PropertyNotSetInConstructor + * @codeCoverageIgnore */ final class CreateSchema extends CliCommand { diff --git a/src/Commands/ValidateCsv.php b/src/Commands/ValidateCsv.php index 091396c3..3e5da270 100644 --- a/src/Commands/ValidateCsv.php +++ b/src/Commands/ValidateCsv.php @@ -25,6 +25,7 @@ /** * @psalm-suppress PropertyNotSetInConstructor + * @codeCoverageIgnore */ final class ValidateCsv extends CliCommand { diff --git a/src/Commands/ValidateDir.php b/src/Commands/ValidateDir.php index c3785aba..15b11df1 100644 --- a/src/Commands/ValidateDir.php +++ b/src/Commands/ValidateDir.php @@ -21,6 +21,7 @@ /** * @psalm-suppress PropertyNotSetInConstructor + * @codeCoverageIgnore */ final class ValidateDir extends CliCommand { diff --git a/src/Schema.php b/src/Schema.php index acbe0b84..1ebf508f 100644 --- a/src/Schema.php +++ b/src/Schema.php @@ -56,6 +56,8 @@ public function __construct(null|array|string $csvSchemaFilenameOrArray = null) } else { throw new \InvalidArgumentException("Unsupported file extension: {$fileExtension}"); } + } elseif (\is_string($csvSchemaFilenameOrArray)) { + throw new \InvalidArgumentException("Invalid schema data: {$csvSchemaFilenameOrArray}"); } else { $this->filename = null; $this->data = new Data(); diff --git a/tests/Blueprint/RulesTest.php b/tests/Blueprint/RulesTest.php index 56f89de0..3e4cc685 100644 --- a/tests/Blueprint/RulesTest.php +++ b/tests/Blueprint/RulesTest.php @@ -126,6 +126,9 @@ public function testIsBool(): void '"is_bool" at line 0, column "prop". Value "1" is not allowed. Allowed values: ["true", "false"].', (string)$rule->validate('1'), ); + + $rule = new IsBool('prop', false); + isSame(null, $rule->validate('1')); } public function testIsDomain(): void @@ -139,12 +142,15 @@ public function testIsDomain(): void isSame(null, $rule->validate('sub-sub-example.qwerty')); isSame( '"is_domain" at line 0, column "prop". Value "example" is not a valid domain.', - (string)(string)$rule->validate('example'), + (string)$rule->validate('example'), ); isSame( '"is_domain" at line 0, column "prop". Value "" is not a valid domain.', (string)$rule->validate(''), ); + + $rule = new IsDomain('prop', false); + isSame(null, $rule->validate('example')); } public function testIsEmail(): void @@ -154,8 +160,11 @@ public function testIsEmail(): void isSame(null, $rule->validate('user@sub.example.com')); isSame( '"is_email" at line 0, column "prop". Value "user:pass@example.com" is not a valid email.', - (string)(string)$rule->validate('user:pass@example.com'), + (string)$rule->validate('user:pass@example.com'), ); + + $rule = new IsEmail('prop', false); + isSame(null, $rule->validate('user:pass@example.com')); } public function testIsFloat(): void @@ -179,6 +188,9 @@ public function testIsFloat(): void '"is_float" at line 0, column "prop". Value " 1" is not a float number.', (string)$rule->validate(' 1'), ); + + $rule = new IsFloat('prop', false); + isSame(null, $rule->validate(' 1')); } public function testIsInt(): void @@ -195,7 +207,7 @@ public function testIsInt(): void ); isSame( '"is_int" at line 0, column "prop". Value "1.1" is not an integer.', - (string)(string)$rule->validate('1.1'), + (string)$rule->validate('1.1'), ); isSame( '"is_int" at line 0, column "prop". Value "1.0" is not an integer.', @@ -207,12 +219,15 @@ public function testIsInt(): void ); isSame( '"is_int" at line 0, column "prop". Value " 1" is not an integer.', - (string)(string)$rule->validate(' 1'), + (string)$rule->validate(' 1'), ); isSame( '"is_int" at line 0, column "prop". Value "1 " is not an integer.', - (string)(string)$rule->validate('1 '), + (string)$rule->validate('1 '), ); + + $rule = new IsInt('prop', false); + isSame(null, $rule->validate(' 1')); } public function testIsIp(): void @@ -244,6 +259,9 @@ public function testIsLatitude(): void '"is_latitude" at line 0, column "prop". Value "90.1.1.1.1" is not a float number.', (string)$rule->validate('90.1.1.1.1'), ); + + $rule = new IsLatitude('prop', false); + isSame(null, $rule->validate('90.1.1.1.1')); } public function testIsLongitude(): void @@ -269,6 +287,9 @@ public function testIsLongitude(): void '"is_longitude" at line 0, column "prop". Value "1.0.0.0" is not a float number.', (string)$rule->validate('1.0.0.0'), ); + + $rule = new IsLongitude('prop', false); + isSame(null, $rule->validate('1.0.0.0')); } public function testIsUrl(): void @@ -285,6 +306,9 @@ public function testIsUrl(): void '"is_url" at line 0, column "prop". Value "//example.com" is not a valid URL.', (string)$rule->validate('//example.com'), ); + + $rule = new IsUrl('prop', false); + isSame(null, $rule->validate('//example.com')); } public function testMin(): void @@ -436,6 +460,9 @@ public function testNotEmpty(): void '"not_empty" at line 0, column "prop". Value is empty.', (string)$rule->validate(null), ); + + $rule = new NotEmpty('prop', false); + isSame(null, $rule->validate(null)); } public function testOnlyCapitalize(): void @@ -454,6 +481,9 @@ public function testOnlyCapitalize(): void '"only_capitalize" at line 0, column "prop". Value "qwe Rty" should be in capitalize.', (string)$rule->validate('qwe Rty'), ); + + $rule = new OnlyCapitalize('prop', false); + isSame(null, $rule->validate('qwerty')); } public function testOnlyLowercase(): void @@ -472,6 +502,9 @@ public function testOnlyLowercase(): void '"only_lowercase" at line 0, column "prop". Value "qwe Rty" should be in lowercase.', (string)$rule->validate('qwe Rty'), ); + + $rule = new OnlyLowercase('prop', false); + isSame(null, $rule->validate('Qwerty')); } public function testOnlyUppercase(): void @@ -489,6 +522,9 @@ public function testOnlyUppercase(): void '"only_uppercase" at line 0, column "prop". Value "qwe Rty" is not uppercase.', (string)$rule->validate('qwe Rty'), ); + + $rule = new OnlyUppercase('prop', false); + isSame(null, $rule->validate('Qwerty')); } public function testPrecision(): void @@ -590,6 +626,9 @@ public function testUsaMarketName(): void 'Market name must have format "New York, NY".', (string)$rule->validate(', ST'), ); + + $rule = new UsaMarketName('prop', false); + isSame(null, $rule->validate(', ST')); } public function testIsUuid4(): void @@ -600,5 +639,8 @@ public function testIsUuid4(): void '"is_uuid4" at line 0, column "prop". Value is not a valid UUID v4.', (string)$rule->validate('123'), ); + + $rule = new IsUuid4('prop', false); + isSame(null, $rule->validate('123')); } } diff --git a/tests/Blueprint/ValidatorTest.php b/tests/Blueprint/ValidatorTest.php index 8846c892..d3ac054f 100644 --- a/tests/Blueprint/ValidatorTest.php +++ b/tests/Blueprint/ValidatorTest.php @@ -17,8 +17,11 @@ namespace JBZoo\PHPUnit\Blueprint; use JBZoo\CsvBlueprint\Csv\CsvFile; +use JBZoo\CsvBlueprint\Validators\ErrorSuite; use JBZoo\PHPUnit\PHPUnit; +use function JBZoo\Data\json; +use function JBZoo\PHPUnit\isContain; use function JBZoo\PHPUnit\isSame; final class ValidatorTest extends PHPUnit @@ -30,6 +33,9 @@ final class ValidatorTest extends PHPUnit private const SCHEMA_SIMPLE_HEADER = PROJECT_TESTS . '/schemas/simple_header.yml'; private const SCHEMA_SIMPLE_NO_HEADER = PROJECT_TESTS . '/schemas/simple_no_header.yml'; + private const SCHEMA_SIMPLE_HEADER_PHP = PROJECT_TESTS . '/schemas/simple_header.php'; + private const SCHEMA_SIMPLE_HEADER_JSON = PROJECT_TESTS . '/schemas/simple_header.json'; + protected function setUp(): void { \date_default_timezone_set('UTC'); @@ -56,6 +62,30 @@ public function testValidWithoutHeader(): void isSame('', (string)$csv->validate()); } + public function testInvalidSchemaFile(): void + { + $this->expectExceptionMessage('Invalid schema data: undefined_file_name.yml'); + $csv = new CsvFile(self::CSV_SIMPLE_HEADER, 'undefined_file_name.yml'); + } + + public function testSchemaAsPhpFile(): void + { + $csv = new CsvFile(self::CSV_SIMPLE_HEADER, self::SCHEMA_SIMPLE_HEADER_PHP); + isSame( + '"min" at line 2, column "0:seq". Value "1" is less than "2".', + (string)$csv->validate(), + ); + } + + public function testSchemaAsJsonFile(): void + { + $csv = new CsvFile(self::CSV_SIMPLE_HEADER, self::SCHEMA_SIMPLE_HEADER_JSON); + isSame( + '"min" at line 2, column "0:seq". Value "1" is less than "2".', + (string)$csv->validate(), + ); + } + public function testNotEmptyMessage(): void { $csv = new CsvFile(self::CSV_COMPLEX, $this->getRule('seq', 'not_empty', true)); @@ -360,6 +390,165 @@ public function testIsEmail(): void ); } + public function testQuickStop(): void + { + $csv = new CsvFile(self::CSV_COMPLEX, $this->getRule('yn', 'is_email', true)); + isSame(1, $csv->validate(true)->count()); + + $csv = new CsvFile(self::CSV_COMPLEX, $this->getRule('yn', 'is_email', true)); + isSame(100, $csv->validate(false)->count()); + + $csv = new CsvFile(self::CSV_COMPLEX, $this->getRule('yn', 'is_email', true)); + isSame(100, $csv->validate()->count()); + } + + public function testErrorToArray(): void + { + $csv = new CsvFile(self::CSV_COMPLEX, $this->getRule('yn', 'is_email', true)); + isSame([ + 'ruleCode' => 'is_email', + 'message' => 'Value "N" is not a valid email', + 'columnName' => '0:yn', + 'line' => 2, + ], $csv->validate(true)->get(0)->toArray()); + } + + public function testRenderText(): void + { + $csv = new CsvFile(self::CSV_SIMPLE_HEADER, $this->getRule('seq', 'min', 3)); + isSame( + '"min" at line 2, column "0:seq". Value "1" is less than "3".', + $csv->validate(true)->render(ErrorSuite::RENDER_TEXT), + ); + + isSame( + \implode("\n", [ + '"min" at line 2, column "0:seq". Value "1" is less than "3".', + '"min" at line 3, column "0:seq". Value "2" is less than "3".', + ]), + $csv->validate()->render(ErrorSuite::RENDER_TEXT), + ); + } + + public function testRenderTable(): void + { + $csv = new CsvFile(self::CSV_SIMPLE_HEADER, $this->getRule('seq', 'min', 3)); + isSame( + \implode("\n", [ + '+------+---------- simple_header.csv ------------------+', + '| Line | id:Column | Rule | Message |', + '+------+-----------+------+----------------------------+', + '| 2 | 0:seq | min | Value "1" is less than "3" |', + '+------+---------- simple_header.csv ------------------+', + '', + ]), + $csv->validate(true)->render(ErrorSuite::RENDER_TABLE), + ); + + isSame( + \implode("\n", [ + '+------+---------- simple_header.csv ------------------+', + '| Line | id:Column | Rule | Message |', + '+------+-----------+------+----------------------------+', + '| 2 | 0:seq | min | Value "1" is less than "3" |', + '| 3 | 0:seq | min | Value "2" is less than "3" |', + '+------+---------- simple_header.csv ------------------+', + '', + ]), + $csv->validate()->render(ErrorSuite::RENDER_TABLE), + ); + } + + public function testRenderTeamCity(): void + { + $csv = new CsvFile(self::CSV_SIMPLE_HEADER, $this->getRule('seq', 'min', 3)); + $out = $csv->validate()->render(ErrorSuite::RENDER_TEAMCITY); + + isContain("##teamcity[testCount count='2' ", $out); + isContain("##teamcity[testSuiteStarted name='simple_header.csv' ", $out); + isContain("##teamcity[testStarted name='min at column 0:seq' locationHint='php_qn://simple_header.csv'", $out); + isContain("##teamcity[testFinished name='min at column 0:seq' timestamp", $out); + isContain('Value "1" is less than "3"', $out); + isContain('Value "2" is less than "3"', $out); + isContain("##teamcity[testSuiteFinished name='simple_header.csv'", $out); + } + + public function testRenderGithub(): void + { + $csv = new CsvFile(self::CSV_SIMPLE_HEADER, $this->getRule('seq', 'min', 3)); + isSame( + \implode("\n", [ + '::error file=simple_header.csv,line=2::min at column 0:seq%0AValue "1" is less than "3"', + '', + '::error file=simple_header.csv,line=3::min at column 0:seq%0AValue "2" is less than "3"', + '', + ]), + $csv->validate()->render(ErrorSuite::RENDER_GITHUB), + ); + } + + public function testRenderGitlab(): void + { + $csv = new CsvFile(self::CSV_SIMPLE_HEADER, $this->getRule('seq', 'min', 3)); + isSame( + [ + [ + 'description' => "min at column 0:seq\nValue \"1\" is less than \"3\"", + 'fingerprint' => 'ec0612c9f1610d440b558fff51bbceed086a0212cdeb14d79d09c8a9bd108487', + 'severity' => 'major', + 'location' => [ + 'path' => 'simple_header.csv', + 'lines' => ['begin' => 2], + ], + ], + [ + 'description' => "min at column 0:seq\nValue \"2\" is less than \"3\"", + 'fingerprint' => '51f82750d029c395dec5f2f5c1c4eb841e43c1ea6b8ece9ee31126a3e22620cb', + 'severity' => 'major', + 'location' => [ + 'path' => 'simple_header.csv', + 'lines' => ['begin' => 3], + ], + ], + ], + json($csv->validate()->render(ErrorSuite::RENDER_GITLAB))->getArrayCopy(), + ); + } + + public function testRenderJUnit(): void + { + $csv = new CsvFile(self::CSV_SIMPLE_HEADER, $this->getRule('seq', 'min', 3)); + isSame( + \implode("\n", [ + '', + '', + ' ', + ' ', + ' Value "1" is less than "3"', + ' ', + ' ', + ' Value "2" is less than "3"', + ' ', + ' ', + '', + '', + ]), + $csv->validate()->render(ErrorSuite::RENDER_JUNIT), + ); + } + + public function testGetAvaiableRenderFormats(): void + { + isSame([ + 'text', + 'table', + 'github', + 'gitlab', + 'teamcity', + 'junit', + ], ErrorSuite::getAvaiableRenderFormats()); + } + private function getRule(?string $columnName, ?string $ruleName, array|bool|float|int|string $options): array { return ['columns' => [['name' => $columnName, 'rules' => [$ruleName => $options]]]]; diff --git a/tests/schemas/simple_header.json b/tests/schemas/simple_header.json new file mode 100644 index 00000000..6aedd968 --- /dev/null +++ b/tests/schemas/simple_header.json @@ -0,0 +1,12 @@ +{ + "columns" : [ + { + "name" : "seq", + "rules" : {"not_empty" : true, "min" : 2} + }, + { + "name" : "bool", + "rules" : {"not_empty" : true} + } + ] +} diff --git a/tests/schemas/simple_header.php b/tests/schemas/simple_header.php new file mode 100644 index 00000000..25b457f2 --- /dev/null +++ b/tests/schemas/simple_header.php @@ -0,0 +1,28 @@ + [ + [ + 'name' => 'seq', + 'rules' => ['not_empty' => true, 'min' => 2], + ], + [ + 'name' => 'bool', + 'rules' => ['not_empty' => true], + ], + ], +];