1<?php 2 3namespace FINDOLOGIC\Export\Tests; 4 5use DateTime; 6use FINDOLOGIC\Export\Data\Attribute; 7use FINDOLOGIC\Export\Data\Image; 8use FINDOLOGIC\Export\Data\Keyword; 9use FINDOLOGIC\Export\Data\Ordernumber; 10use FINDOLOGIC\Export\Data\Price; 11use FINDOLOGIC\Export\Data\Property; 12use FINDOLOGIC\Export\Data\Usergroup; 13use FINDOLOGIC\Export\Exporter; 14use FINDOLOGIC\Export\Helpers\XMLHelper; 15use FINDOLOGIC\Export\XML\XMLExporter; 16use FINDOLOGIC\Export\XML\XMLItem; 17use PHPUnit\Framework\TestCase; 18 19/** 20 * Class XmlSerializationTest 21 * @package FINDOLOGIC\Tests 22 * 23 * Tests that the serialized output adheres to the defined schema and to things that cannot be covered by it. 24 * 25 * The schema is fetched from GitHub every time the test case is run, to ensure that tests still pass in case the schema 26 * changed in the meantime. 27 */ 28class XmlSerializationTest extends TestCase 29{ 30 const SCHEMA_URL = 'https://raw.githubusercontent.com/findologic/xml-export/master/src/main/resources/findologic.xsd'; 31 32 private static $schema; 33 34 public static function setUpBeforeClass() 35 { 36 parent::setUpBeforeClass(); 37 38 // Download the schema once for the whole test case for speed as compared to downloading it for each test. 39 self::$schema = file_get_contents(self::SCHEMA_URL); 40 } 41 42 public function tearDown() 43 { 44 try { 45 unlink('/tmp/findologic_0_1.xml'); 46 } catch (\Exception $e) { 47 // No need to delete a written file if the test didn't write it. 48 } 49 } 50 51 /** @var XMLExporter */ 52 private $exporter; 53 54 /** 55 * @SuppressWarnings(PHPMD.StaticAccess) 56 */ 57 public function setUp() 58 { 59 $this->exporter = Exporter::create(Exporter::TYPE_XML); 60 } 61 62 private function getMinimalItem() 63 { 64 $item = $this->exporter->createItem('123'); 65 66 $price = new Price(); 67 $price->setValue('13.37'); 68 $item->setPrice($price); 69 70 return $item; 71 } 72 73 private function assertPageIsValid($xmlString) 74 { 75 $xmlDocument = new \DOMDocument('1.0', 'utf-8'); 76 $xmlDocument->loadXML($xmlString); 77 78 $this->assertTrue($xmlDocument->schemaValidateSource(self::$schema)); 79 } 80 81 public function testEmptyPageIsValid() 82 { 83 $page = $this->exporter->serializeItems([], 0, 0, 0); 84 85 $this->assertPageIsValid($page); 86 } 87 88 public function testMinimalItemIsValid() 89 { 90 $item = $this->getMinimalItem(); 91 $page = $this->exporter->serializeItems([$item], 0, 1, 1); 92 93 $this->assertPageIsValid($page); 94 } 95 96 /** 97 * @expectedException \FINDOLOGIC\Export\XML\ItemsExceedCountValueException 98 */ 99 public function testMoreItemsSuppliedThanCountValueCausesException() 100 { 101 $items = []; 102 103 for ($i = 0; $i <= 2; $i++) { 104 $item = $this->exporter->createItem((string)$i); 105 106 $price = new Price(); 107 //Generate a random price 108 $price->setValue(rand(1, 2000)*1.24); 109 $item->setPrice($price); 110 111 $items[] = $item; 112 } 113 114 $this->exporter->serializeItems($items, 0, 1, 1); 115 } 116 117 public function testPropertyKeysAndValuesAreCdataWrapped() 118 { 119 $item = $this->getMinimalItem(); 120 121 $property = new Property('"</>', [null => '"</>']); 122 $item->addProperty($property); 123 124 $page = $this->exporter->serializeItems([$item], 0, 1, 1); 125 126 $this->assertPageIsValid($page); 127 } 128 129 public function testAttributesAreCdataWrapped() 130 { 131 $item = $this->getMinimalItem(); 132 133 $attribute = new Attribute('"</>', ['"</>', 'regular']); 134 $item->addAttribute($attribute); 135 136 $page = $this->exporter->serializeItems([$item], 0, 1, 1); 137 138 $this->assertPageIsValid($page); 139 } 140 141 public function testImagesCanBeDefaultAndThumbnail() 142 { 143 $item = $this->getMinimalItem(); 144 145 $item->setAllImages([ 146 new Image('http://example.org/default.png'), 147 new Image('http://example.org/thumbnail.png', Image::TYPE_THUMBNAIL), 148 new Image('http://example.org/ug_default.png', Image::TYPE_DEFAULT, 'usergroup'), 149 ]); 150 151 $page = $this->exporter->serializeItems([$item], 0, 1, 1); 152 153 $this->assertPageIsValid($page); 154 } 155 156 public function testBaseImageCanBeExported() 157 { 158 $item = $this->getMinimalItem(); 159 160 $imageUrl = 'http://example.org/thumbnail.png'; 161 $item->setAllImages([ 162 new Image($imageUrl), 163 ]); 164 165 $document = new \DOMDocument('1.0', 'utf-8'); 166 $root = XMLHelper::createElement($document, 'findologic', ['version' => '1.0']); 167 $document->appendChild($root); 168 169 $xmlItems = XMLHelper::createElement($document, 'items', [ 170 'start' => 0, 171 'count' => 1, 172 'total' => 1 173 ]); 174 $root->appendChild($xmlItems); 175 176 $itemDom = $item->getDomSubtree($document); 177 178 foreach ($itemDom->childNodes as $node) { 179 if ($node->nodeName === 'allImages') { 180 $this->assertEquals($imageUrl, $node->nodeValue); 181 } 182 } 183 } 184 185 /** 186 * @expectedException \FINDOLOGIC\Export\Data\BaseImageMissingException 187 */ 188 public function testMissingBaseImageCausesException() 189 { 190 $item = $this->getMinimalItem(); 191 192 $item->setAllImages([ 193 new Image('http://example.org/thumbnail.png', Image::TYPE_THUMBNAIL), 194 new Image('http://example.org/ug_default.png', Image::TYPE_THUMBNAIL, 'usergroup'), 195 ]); 196 197 $this->exporter->serializeItems([$item], 0, 1, 1); 198 } 199 200 /** 201 * @expectedException \FINDOLOGIC\Export\Data\ImagesWithoutUsergroupMissingException 202 */ 203 public function testImagesWithoutUsergroupMissingCausesException() 204 { 205 $item = $this->getMinimalItem(); 206 207 $item->setAllImages([ 208 new Image('http://example.org/ug_default.png', Image::TYPE_DEFAULT, 'usergroup'), 209 ]); 210 211 $this->exporter->serializeItems([$item], 0, 1, 1); 212 } 213 214 public function testOrdernumbersSupportUsergroups() 215 { 216 $item = $this->getMinimalItem(); 217 218 $item->setAllOrdernumbers([ 219 new Ordernumber('137-42-23.7'), 220 new Ordernumber('137-42-23.7-A', 'usergroup'), 221 ]); 222 223 $page = $this->exporter->serializeItems([$item], 0, 1, 1); 224 225 $this->assertPageIsValid($page); 226 } 227 228 public function testKeywordsSupportUsergroups() 229 { 230 $item = $this->getMinimalItem(); 231 232 $item->setAllKeywords([ 233 new Keyword('awesome "</>]]>7'), 234 new Keyword('restricted', 'usergroup'), 235 ]); 236 237 $page = $this->exporter->serializeItems([$item], 0, 1, 1); 238 239 $this->assertPageIsValid($page); 240 } 241 242 public function testUsergroupVisibilitiesAreExported() 243 { 244 $item = $this->getMinimalItem(); 245 246 $item->setAllUsergroups([ 247 new Usergroup('one group'), 248 new Usergroup('another group') 249 ]); 250 251 $page = $this->exporter->serializeItems([$item], 0, 1, 1); 252 253 $this->assertPageIsValid($page); 254 } 255 256 public function testXmlCanBeWrittenDirectlyToFile() 257 { 258 $item = $this->getMinimalItem(); 259 260 $expectedXml = $this->exporter->serializeItems([$item], 0, 1, 1); 261 $this->exporter->serializeItemsToFile('/tmp', [$item], 0, 1, 1); 262 263 self::assertEquals($expectedXml, file_get_contents('/tmp/findologic_0_1.xml')); 264 } 265 266 public function testAttemptingToGetCsvFromAnXmlItemResultsInAnException() 267 { 268 $item = new XMLItem(123); 269 270 try { 271 $item->getCsvFragment(); 272 } catch (\BadMethodCallException $e) { 273 $this->assertEquals('XMLItem does not implement CSV export.', $e->getMessage()); 274 } 275 } 276 277 /** 278 * @return array Name of add method to call in a test to add a certain value, and an array of values with 279 * usergroup names as key. 280 */ 281 public function simpleValueAddingShortcutProvider() 282 { 283 $stringValuesWithUsergroupKeys = [ 284 '' => 'No usergroup', 285 'foo' => 'One usergroup', 286 'bar' => 'Another usergroup' 287 ]; 288 289 $integerValuesWithUsergroupKeys = [ 290 '' => 0, 291 'foo' => 3, 292 'bar' => 10 293 ]; 294 295 return [ 296 'name' => ['Name', $stringValuesWithUsergroupKeys], 297 'summary' => ['Summary', $stringValuesWithUsergroupKeys], 298 'description' => ['Description', $stringValuesWithUsergroupKeys], 299 'price' => ['Price', [ 300 '' => 13.37, 301 'foo' => 42, 302 'bar' => 12.00 303 ]], 304 'url' => ['Url', [ 305 '' => 'https://example.org/product.html', 306 'foo' => 'https://example.org/product.html?group=foo', 307 'bar' => 'https://example.org/product.html?group=bar' 308 ]], 309 'bonus' => ['Bonus', $integerValuesWithUsergroupKeys], 310 'sales frequency' => ['SalesFrequency', $integerValuesWithUsergroupKeys], 311 'sort' => ['Sort', $integerValuesWithUsergroupKeys], 312 ]; 313 } 314 315 /** 316 * @dataProvider simpleValueAddingShortcutProvider 317 * 318 * @param string $valueType 319 * @param array $values 320 */ 321 public function testSimpleValuesAddedToItemViaShortcutAccumulate($valueType, array $values) 322 { 323 $item = new XMLItem(123); 324 325 foreach ($values as $usergroup => $value) { 326 $item->{'add' . $valueType}($value, $usergroup); 327 } 328 329 $this->assertEquals($values, $item->{'get' . $valueType}()->getValues()); 330 } 331 332 public function testDateValuesAddedToItemViaShortcutAccumulate() 333 { 334 $values = [ 335 '' => new DateTime('today midnight'), 336 'foo' => new DateTime('yesterday midnight'), 337 'bar' => new DateTime('tomorrow midnight') 338 ]; 339 340 // On assignment, dates are converted to strings according to the format set in the schema. 341 $expectedValues = array_map(function (DateTime $date) { 342 return $date->format(DATE_ATOM); 343 }, $values); 344 345 $item = new XMLItem(123); 346 347 foreach ($values as $usergroup => $value) { 348 $item->addDateAdded($value, $usergroup); 349 } 350 351 $this->assertEquals($expectedValues, $item->getDateAdded()->getValues()); 352 } 353} 354