From 5ef0160145e7f39f69eab6f610e979284933bd8a Mon Sep 17 00:00:00 2001 From: Shikiryu Date: Wed, 19 Apr 2023 17:03:59 +0200 Subject: [PATCH] =?UTF-8?q?=F0=9F=A7=AA=20Add=20failing=20tests?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * :construction: Add failing tests Closes #3 * 🧪 Add failing tests Closes #3 * 🧪 Failing tests makes somes rules pop better --------- Co-authored-by: Clement Desmidt --- src/Entity/Channel.php | 4 +- src/Entity/Item.php | 11 + src/Exception/InvalidPropertyException.php | 12 ++ src/Parser/SRSSParser.php | 12 ++ src/SRSS.php | 37 ++-- src/Validator/Formator.php | 2 +- src/Validator/HasValidator.php | 21 +- src/Validator/Validator.php | 49 ++++- tests/CompleteBuilderTest.php | 4 +- tests/FailingTest.php | 230 +++++++++++++++++++++ 10 files changed, 343 insertions(+), 39 deletions(-) create mode 100644 src/Exception/InvalidPropertyException.php create mode 100644 tests/FailingTest.php diff --git a/src/Entity/Channel.php b/src/Entity/Channel.php index c745efd..80f4468 100644 --- a/src/Entity/Channel.php +++ b/src/Entity/Channel.php @@ -96,12 +96,12 @@ class Channel extends HasValidator implements SRSSElement * @validate hour * @format hour */ - protected ?string $skipHours = null; + protected ?array $skipHours = null; /** * @validate day * @format day */ - protected ?string $skipDays = null; + protected ?array $skipDays = null; /** * @return bool diff --git a/src/Entity/Item.php b/src/Entity/Item.php index 8e43f6a..a50fe93 100644 --- a/src/Entity/Item.php +++ b/src/Entity/Item.php @@ -12,6 +12,17 @@ use Shikiryu\SRSS\Validator\Validator; /** * https://cyber.harvard.edu/rss/rss.html#hrelementsOfLtitemgt + * @property null|string title + * @property null|string link + * @property null|string description + * @property null|string author + * @property null|array category + * @property null|string comments + * @property null|Enclosure enclosure + * @property null|string guid + * @property null|string pubDate + * @property null|Source source + * @property array medias */ class Item extends HasValidator implements SRSSElement { diff --git a/src/Exception/InvalidPropertyException.php b/src/Exception/InvalidPropertyException.php new file mode 100644 index 0000000..818f855 --- /dev/null +++ b/src/Exception/InvalidPropertyException.php @@ -0,0 +1,12 @@ +value = $child->nodeValue; $this->doc->channel->category = $category; + } elseif ($child->nodeName === 'skipHours') { + foreach ($child->childNodes as $hour) { + if ($hour->nodeType === XML_ELEMENT_NODE) { + $this->doc->channel->skipHours = $hour->nodeValue; + } + } + } elseif ($child->nodeName === 'skipDays') { + foreach ($child->childNodes as $day) { + if ($day->nodeType === XML_ELEMENT_NODE) { + $this->doc->channel->skipDays = $day->nodeValue; + } + } } else { $this->doc->channel->{$child->nodeName} = $child->nodeValue; } diff --git a/src/SRSS.php b/src/SRSS.php index 4296286..1d86f89 100644 --- a/src/SRSS.php +++ b/src/SRSS.php @@ -11,6 +11,7 @@ use Shikiryu\SRSS\Entity\Channel\Cloud; use Shikiryu\SRSS\Entity\Channel\Image; use Shikiryu\SRSS\Entity\Item; use Shikiryu\SRSS\Exception\ChannelNotFoundInRSSException; +use Shikiryu\SRSS\Exception\InvalidPropertyException; use Shikiryu\SRSS\Exception\PropertyNotFoundException; use Shikiryu\SRSS\Exception\SRSSException; use Shikiryu\SRSS\Exception\UnreadableRSSException; @@ -27,17 +28,18 @@ use Shikiryu\SRSS\Validator\Validator; * @property null|string $managingEditor * @property null|string $webMaster * @property null|string $pubDate - * @property null|string $lastBuildDate + * @property null|string $lastBuildDate * @property null|Category[] $category - * @property null|string $generator - * @property null|string $docs - * @property null|Cloud $cloud - * @property null|string $ttl - * @property null|Image $image - * @property null|string $rating - * @property null|string $textInput - * @property null|string $skipHours - * @property null|string $skipDays + * @property null|string $generator + * @property null|string $docs + * @property null|Cloud $cloud + * @property null|string $ttl + * @property null|Image $image + * @property null|string $rating + * @property null|string $textInput + * @property null|string $skipHours + * @property null|string $skipDays + * @property string|null $validated */ class SRSS implements Iterator { @@ -126,15 +128,16 @@ class SRSS implements Iterator if (!property_exists(Channel::class, $name)) { throw new PropertyNotFoundException(Channel::class, $name); } - if ((new Validator())->isValidValueForObjectProperty($this->channel, $name, $val)) { - if (SRSSTools::getPropertyType(Channel::class, $name) === 'array') { - $this->channel->{$name} = $val; - } else { - $val = is_string($val) ? (new Formator())->formatValue($this->channel, $name, $val) : $val; - $this->channel->{$name} = $val; - } + if (!(new Validator())->isValidValueForObjectProperty($this->channel, $name, $val)) { + throw new InvalidPropertyException(get_class($this), $name, $val); + } + if (SRSSTools::getPropertyType(Channel::class, $name) === 'array') { + $this->channel->{$name} = $val; + } else { + $val = is_string($val) ? (new Formator())->formatValue($this->channel, $name, $val) : $val; + $this->channel->{$name} = $val; } } diff --git a/src/Validator/Formator.php b/src/Validator/Formator.php index b81ced9..924ecd1 100644 --- a/src/Validator/Formator.php +++ b/src/Validator/Formator.php @@ -143,7 +143,7 @@ class Formator return $check; } - return sprintf('', htmlspecialchars($check)); + return sprintf('', $check); } /** diff --git a/src/Validator/HasValidator.php b/src/Validator/HasValidator.php index 5017501..8896080 100644 --- a/src/Validator/HasValidator.php +++ b/src/Validator/HasValidator.php @@ -2,6 +2,7 @@ namespace Shikiryu\SRSS\Validator; +use Shikiryu\SRSS\Exception\InvalidPropertyException; use Shikiryu\SRSS\Exception\PropertyNotFoundException; use Shikiryu\SRSS\Exception\SRSSException; use Shikiryu\SRSS\SRSSTools; @@ -27,17 +28,19 @@ abstract class HasValidator if (!property_exists(static::class, $name)) { throw new PropertyNotFoundException(static::class, $name); } - if ((new Validator())->isValidValueForObjectProperty($this, $name, $val)) { - - if (SRSSTools::getPropertyType(static::class, $name) === 'array') { - /** @var array $this->{$name} */ - $this->{$name}[] = $val; - } else { - $val = is_string($val) ? (new Formator())->formatValue($this, $name, $val) : $val; - $this->{$name} = $val; - } + if (!(new Validator())->isValidValueForObjectProperty($this, $name, $val)) { + throw new InvalidPropertyException(get_class($this), $name, $val); } + + if (SRSSTools::getPropertyType(static::class, $name) === 'array') { + /** @var array $this->{$name} */ + $this->{$name}[] = $val; + } else { + $val = is_string($val) ? (new Formator())->formatValue($this, $name, $val) : $val; + $this->{$name} = $val; + } + } /** diff --git a/src/Validator/Validator.php b/src/Validator/Validator.php index 7bdd03e..f69700a 100644 --- a/src/Validator/Validator.php +++ b/src/Validator/Validator.php @@ -24,6 +24,7 @@ class Validator */ public function isValidValueForObjectProperty($object, $property, $value): bool { + $this->object = $object; try { $property = $this->getReflectedProperty($object, $property); } catch (ReflectionException) { @@ -31,17 +32,19 @@ class Validator } $propertyAnnotations = $this->_getPropertyAnnotations($property); - if (empty($value) && !in_array('required', $propertyAnnotations, true)) { + if (empty($value) && count(array_filter($propertyAnnotations, static fn($rule) => str_starts_with($rule, 'required'))) === 0) { return true; } foreach ($propertyAnnotations as $propertyAnnotation) { $annotation = explode(' ', $propertyAnnotation); - $object->validated[$property->name] = $this->_validateProperty($annotation, $value); + if ($this->_validateProperty($annotation, $value) === false) { + return false; + } } - return count(array_filter($object->validated, static fn($v) => ($v !== null && $v === false))) === 0; + return true; } /** @@ -69,9 +72,10 @@ class Validator */ public function isObjectValid($object): bool { - if (!$object->validated) { + $object->validated = []; +// if (!$object->validated) { $object = $this->validateObject($object); - } +// } return !in_array(false, $object->validated, true); } @@ -91,7 +95,7 @@ class Validator foreach ($properties as $property) { $propertyValue = $object->{$property['name']}; - if (empty($propertyValue) && !in_array('required', $property['rules'], true)) { + if (empty($propertyValue) && count(array_filter($property['rules'], static fn($rule) => str_starts_with($rule, 'required'))) === 0) { continue; } @@ -117,7 +121,7 @@ class Validator $args_annotation = array_splice($annotation, 1); - return $this->{sprintf('_validate%s', ucfirst($annotation[0]))}($property, ...$args_annotation); + return $this->{sprintf('_validate%s', ucfirst($annotation[0]))}($property, $args_annotation); } @@ -197,18 +201,38 @@ class Validator private function _validateHour($value): bool { + if (is_array($value)) { + foreach ($value as $val) { + if ($this->_validateHour($val) === false) { + return false; + } + } + + return true; + } + $options = [ 'options' => [ - 'default' => 0, 'min_range' => 0, 'max_range' => 23 ] ]; + return filter_var($value, FILTER_VALIDATE_INT, $options) !== false; } private function _validateDay($value): bool { + if (is_array($value)) { + foreach ($value as $val) { + if ($this->_validateDay($val) === false) { + return false; + } + } + + return true; + } + return in_array( strtolower($value), ['monday', 'tuesday', 'wednesday', 'thursday', 'friday', 'saturday', 'sunday'] @@ -251,4 +275,13 @@ class Validator { return filter_var($value, FILTER_VALIDATE_EMAIL); } + + private function _validateMax($value, array $max): bool + { + return $value <= current($max); + } + private function _validateMin($value, array $max): bool + { + return $value >= current($max); + } } \ No newline at end of file diff --git a/tests/CompleteBuilderTest.php b/tests/CompleteBuilderTest.php index f8bcb80..d210203 100644 --- a/tests/CompleteBuilderTest.php +++ b/tests/CompleteBuilderTest.php @@ -37,8 +37,8 @@ class CompleteBuilderTest extends TestCase $image->title = 'title of image'; $rating = 'yes'; $textInput = 'ignore'; - $skipDays = 'monday'; - $skipHours = '8'; + $skipDays = ['monday']; + $skipHours = ['8', '9']; $srss = SRSS::create(); $srss->title = $title; diff --git a/tests/FailingTest.php b/tests/FailingTest.php new file mode 100644 index 0000000..7dacf65 --- /dev/null +++ b/tests/FailingTest.php @@ -0,0 +1,230 @@ +isValid(), var_export($rss->validated, true)); + $rss->title = 'title'; // mandatory + self::assertFalse($rss->isValid(), var_export($rss->validated, true)); + $rss->description = 'desc'; // mandatory + self::assertFalse($rss->isValid(), var_export($rss->validated, true)); + $rss->link = 'https://example.org'; + self::assertTrue($rss->isValid(), var_export($rss->validated, true)); + } + + public function testInvalidChannelLink(): void + { + $this->expectException(InvalidPropertyException::class); + $rss = SRSS::create(); + $rss->title = 'title'; // mandatory + $rss->description = 'desc'; // mandatory + $rss->link = 'desc'; // mandatory but should be an url + } + + public function testInvalidChannelLanguage(): void + { + $rss = SRSS::create(); + $this->expectException(InvalidPropertyException::class); + $rss->language = 'en-en'; // should be a valid language + } + + public function testInvalidChannelCopyright(): void + { + $rss = SRSS::create(); + $this->expectException(InvalidPropertyException::class); + $rss->copyright = 'test'; // should not have html element + } + + public function testInvalidChannelManagingEditor(): void + { + $rss = SRSS::create(); + $this->expectException(InvalidPropertyException::class); + $rss->managingEditor = 'test'; // should not have html element + } + + public function testInvalidChannelWebmaster(): void + { + $rss = SRSS::create(); + $this->expectException(InvalidPropertyException::class); + $rss->webMaster = 'test'; // should not have html element + } + + public function testInvalidChannelPubDate(): void + { + $rss = SRSS::create(); + $this->expectException(InvalidPropertyException::class); + $rss->pubDate = 'test'; // should be a valid date + } + + public function testInvalidChannelLastBuildDate(): void + { + $rss = SRSS::create(); + $this->expectException(InvalidPropertyException::class); + $rss->lastBuildDate = 'test'; // should be a valid date + } + + public function testInvalidChannelGenerator(): void + { + $rss = SRSS::create(); + $this->expectException(InvalidPropertyException::class); + $rss->generator = 'test'; // should not have html element + } + + public function testInvalidChannelDocs(): void + { + $rss = SRSS::create(); + $this->expectException(InvalidPropertyException::class); + $rss->docs = 'desc'; //should be a url + } + + public function testInvalidChannelTTL(): void + { + $rss = SRSS::create(); + $this->expectException(InvalidPropertyException::class); + $rss->ttl = 'desc'; // should be an int + } + + public function testInvalidChannelSkipHours(): void + { + $rss = SRSS::create(); + $this->expectException(InvalidPropertyException::class); + $rss->skipHours = 'desc'; // should be an hour + } + + public function testInvalidChannelSkipDays(): void + { + $rss = SRSS::create(); + $this->expectException(InvalidPropertyException::class); + $rss->skipDays = 'desc'; // should be a day + } + + public function testInvalidItemAuthor(): void + { + $item = new Shikiryu\SRSS\Entity\Item(); + $this->expectException(InvalidPropertyException::class); + $item->author = 'test'; // should be an email + } + + public function testInvalidItemComments(): void + { + $item = new Shikiryu\SRSS\Entity\Item(); + $this->expectException(InvalidPropertyException::class); + $item->comments = 'test'; // should be an url + } + + public function testInvalidItemMandatory(): void + { + $item = new Shikiryu\SRSS\Entity\Item(); + $item->title = 'title'; + self::assertTrue($item->isValid(), var_export($item->validated, true)); + $item->description = 'desc'; + $item->title = null; + self::assertTrue($item->isValid(), var_export($item->validated, true)); + + $this->expectException(InvalidPropertyException::class); + $item->title = null; + $item->description = null; + } + + public function testChannelCategoryDomain(): void + { + $category = new Category(); + $this->expectException(InvalidPropertyException::class); + $category->domain = 'test'; + } + + public function testChannelCloudPort(): void + { + $cloud = new Cloud(); + $this->expectException(InvalidPropertyException::class); + $cloud->port = 'test'; + } + + public function testChannelImageUrl(): void + { + $image = new Image(); + $this->expectException(InvalidPropertyException::class); + $image->url = 'test'; + } + + public function testChannelImageTitle(): void + { + $image = new Image(); + $this->expectException(InvalidPropertyException::class); + $image->title = 'test'; + } + + public function testChannelImageLink(): void + { + $image = new Image(); + $this->expectException(InvalidPropertyException::class); + $image->link = 'test'; + } + + public function testChannelImageWidthType(): void + { + $image = new Image(); + $this->expectException(InvalidPropertyException::class); + $image->width = 'test'; + } + + public function testChannelImageWidthMax(): void + { + $image = new Image(); + $this->expectException(InvalidPropertyException::class); + $image->width = '150'; + } + + public function testChannelImageHeightType(): void + { + $image = new Image(); + $this->expectException(InvalidPropertyException::class); + $image->height = 'test'; + } + + public function testChannelImageHeightMax(): void + { + $image = new Image(); + $this->expectException(InvalidPropertyException::class); + $image->height = '500'; + } + + public function testItemEnclosureUrl(): void + { + $enclosure = new Enclosure(); + $this->expectException(InvalidPropertyException::class); + $enclosure->url = 'test'; + } + + public function testItemEnclosureLength(): void + { + $enclosure = new Enclosure(); + $this->expectException(InvalidPropertyException::class); + $enclosure->length = 'test'; + } + + public function testItemSourceUrl() + { + $source = new Source(); + $this->expectException(InvalidPropertyException::class); + $source->url = 'test'; + } + + public function testItemSourceValue() + { + $source = new Source(); + $this->expectException(InvalidPropertyException::class); + $source->value = 'test'; + } +}