mirror of
				https://github.com/Chouchen/ShikiryuRSS.git
				synced 2025-10-31 14:33:09 +01:00 
			
		
		
		
	🧪 Add failing tests
* 🚧 Add failing tests Closes #3 * 🧪 Add failing tests Closes #3 * 🧪 Failing tests makes somes rules pop better --------- Co-authored-by: Clement Desmidt <clement@desmidt.fr>
This commit is contained in:
		| @@ -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 | ||||
|   | ||||
| @@ -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 | ||||
| { | ||||
|   | ||||
							
								
								
									
										12
									
								
								src/Exception/InvalidPropertyException.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										12
									
								
								src/Exception/InvalidPropertyException.php
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,12 @@ | ||||
| <?php | ||||
|  | ||||
| namespace Shikiryu\SRSS\Exception; | ||||
|  | ||||
| class InvalidPropertyException extends SRSSException | ||||
| { | ||||
|  | ||||
|     public function __construct(string $object, string $property, ?string $value) | ||||
|     { | ||||
|         parent::__construct(sprintf('Invalid property `%s` = `%s` in `%s`', $property, $value, $object)); | ||||
|     } | ||||
| } | ||||
| @@ -110,6 +110,18 @@ class SRSSParser extends DomDocument | ||||
|                     } | ||||
|                     $category->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; | ||||
|                 } | ||||
|   | ||||
							
								
								
									
										37
									
								
								src/SRSS.php
									
									
									
									
									
								
							
							
						
						
									
										37
									
								
								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; | ||||
|         } | ||||
|     } | ||||
|  | ||||
|   | ||||
| @@ -143,7 +143,7 @@ class Formator | ||||
|             return $check; | ||||
|         } | ||||
|  | ||||
|         return sprintf('<![CDATA[ %s ]]>', htmlspecialchars($check)); | ||||
|         return sprintf('<![CDATA[ %s ]]>', $check); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|   | ||||
| @@ -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; | ||||
|         } | ||||
|  | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|   | ||||
| @@ -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); | ||||
|     } | ||||
| } | ||||
| @@ -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; | ||||
|   | ||||
							
								
								
									
										230
									
								
								tests/FailingTest.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										230
									
								
								tests/FailingTest.php
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,230 @@ | ||||
| <?php | ||||
|  | ||||
| use PHPUnit\Framework\TestCase; | ||||
| use Shikiryu\SRSS\Entity\Channel\Category; | ||||
| use Shikiryu\SRSS\Entity\Channel\Cloud; | ||||
| use Shikiryu\SRSS\Entity\Channel\Image; | ||||
| use Shikiryu\SRSS\Entity\Item\Enclosure; | ||||
| use Shikiryu\SRSS\Entity\Item\Source; | ||||
| use Shikiryu\SRSS\Exception\InvalidPropertyException; | ||||
| use Shikiryu\SRSS\SRSS; | ||||
|  | ||||
| class FailingTest extends TestCase | ||||
| { | ||||
|     public function testInvalidChannelMandatory(): void | ||||
|     { | ||||
|         $rss = SRSS::create(); | ||||
|         self::assertFalse($rss->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 = '<strong>test</strong>'; // should not have html element | ||||
|     } | ||||
|  | ||||
|     public function testInvalidChannelManagingEditor(): void | ||||
|     { | ||||
|         $rss = SRSS::create(); | ||||
|         $this->expectException(InvalidPropertyException::class); | ||||
|         $rss->managingEditor = '<strong>test</strong>'; // should not have html element | ||||
|     } | ||||
|  | ||||
|     public function testInvalidChannelWebmaster(): void | ||||
|     { | ||||
|         $rss = SRSS::create(); | ||||
|         $this->expectException(InvalidPropertyException::class); | ||||
|         $rss->webMaster = '<strong>test</strong>'; // 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 = '<strong>test</strong>'; // 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 = '<strong>test</strong>'; | ||||
|     } | ||||
|  | ||||
|     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 = '<strong>test</strong>'; | ||||
|     } | ||||
| } | ||||
		Reference in New Issue
	
	Block a user
	 Shikiryu
					Shikiryu