diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 0000000..7227c4f --- /dev/null +++ b/.github/workflows/test.yml @@ -0,0 +1,33 @@ +name: Tests + +on: [push, pull_request] + +jobs: + build: + runs-on: ubuntu-latest + strategy: + matrix: + php-versions: ['7.1', '7.2', '7.3', '7.4'] + name: PHP ${{ matrix.php-versions }} Test + steps: + - name: Checkout + uses: actions/checkout@v2 + + - name: Setup PHP + uses: shivammathur/setup-php@v2 + with: + php-version: ${{ matrix.php-versions }} + extensions: mbstring, dom + coverage: xdebug + tools: composer + + - name: Cache dependencies + uses: actions/cache@v1 + with: + path: ~/.composer/cache/files + key: cache-php-${{ matrix.php-versions }}-composer-${{ hashFiles('composer.json') }} + + - name: Run test suite + run: | + composer install + composer test diff --git a/.github/workflows/test_with_coverage.yml b/.github/workflows/test_with_coverage.yml new file mode 100644 index 0000000..c551b4b --- /dev/null +++ b/.github/workflows/test_with_coverage.yml @@ -0,0 +1,45 @@ +name: Tests with coverage + +on: + push: + schedule: + - cron: '0 0 * * *' + +jobs: + build: + runs-on: ubuntu-latest + strategy: + matrix: + php-versions: ['7.1', '7.2', '7.3', '7.4'] + name: PHP ${{ matrix.php-versions }} Test + steps: + - name: Checkout + uses: actions/checkout@v2 + + - name: Setup PHP + uses: shivammathur/setup-php@v2 + with: + php-version: ${{ matrix.php-versions }} + extensions: mbstring, dom + coverage: xdebug + tools: composer + + - name: Cache dependencies + uses: actions/cache@v1 + with: + path: ~/.composer/cache/files + key: cache-php-${{ matrix.php-versions }}-composer-${{ hashFiles('composer.json') }} + + - name: Run test suite + run: | + composer install + composer test + + - name: Upload coverage to Codecov + uses: codecov/codecov-action@v1 + with: + token: ${{ secrets.CODECOV_TOKEN }} + file: ./coverage.xml + flags: unittests + name: codecov-umbrella + fail_ci_if_error: true diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 7a1e53b..0000000 --- a/.travis.yml +++ /dev/null @@ -1,16 +0,0 @@ -language: php - -php: - - 7.1 - - 7.2 - - 7.3 - - 7.4 - -install: - - composer install -script: - - composer test - - composer lint - -after_success: - - bash <(curl -s https://codecov.io/bash) diff --git a/README.md b/README.md index f4bd9eb..095d301 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # FINDOLOGIC export toolkit -[![Travis](https://img.shields.io/travis/findologic/libflexport.svg)](https://travis-ci.org/findologic/libflexport) +![Github Actions](https://github.com/findologic/libflexport/workflows/Tests%20with%20coverage/badge.svg) [![Latest Stable Version](https://poser.pugx.org/findologic/libflexport/v/stable)](https://packagist.org/packages/findologic/libflexport) [![Code Climate](https://codeclimate.com/github/findologic/libflexport.svg)](https://codeclimate.com/github/findologic/libflexport) [![Codecov](https://codecov.io/gh/findologic/libflexport/branch/develop/graph/badge.svg)](https://codecov.io/gh/findologic/libflexport) diff --git a/composer.json b/composer.json index 479b990..9c72796 100644 --- a/composer.json +++ b/composer.json @@ -9,7 +9,7 @@ "findologic/xml-export-schema": "^1.4" }, "require-dev": { - "phpunit/phpunit": "^7.5", + "phpunit/phpunit": "^7.5|^8.5|^9.0", "squizlabs/php_codesniffer": "^3.5" }, "scripts": { @@ -38,6 +38,11 @@ "name": "Mohamad Assani", "email": "m.assani@findologic.com", "role": "developer" + }, + { + "name": "Huzaifa Mustafa", + "email": "24492269+zaifastafa@users.noreply.github.com", + "role": "developer" } ], "autoload": { diff --git a/src/FINDOLOGIC/Export/CSV/CSVExporter.php b/src/FINDOLOGIC/Export/CSV/CSVExporter.php index 00dea79..40e993f 100644 --- a/src/FINDOLOGIC/Export/CSV/CSVExporter.php +++ b/src/FINDOLOGIC/Export/CSV/CSVExporter.php @@ -56,7 +56,9 @@ public function serializeItemsToFile( $csvString = $this->serializeItems($items, $start, $count, $total); $targetPath = sprintf('%s/findologic.csv', $targetDirectory); - file_put_contents($targetPath, $csvString); + // Clear CSV contents if a new export starts. Don't do this for further pagination steps, to prevent + // overriding the file itself, causing it to clear all contents except the new items. + file_put_contents($targetPath, $csvString, $start > 0 ? FILE_APPEND : 0); return $targetPath; } diff --git a/src/FINDOLOGIC/Export/Exceptions/InvalidUrlException.php b/src/FINDOLOGIC/Export/Exceptions/InvalidUrlException.php index 1e7cd95..409edaf 100644 --- a/src/FINDOLOGIC/Export/Exceptions/InvalidUrlException.php +++ b/src/FINDOLOGIC/Export/Exceptions/InvalidUrlException.php @@ -6,8 +6,8 @@ class InvalidUrlException extends RuntimeException { - public function __construct() + public function __construct(string $url) { - parent::__construct('Value is not a valid url!'); + parent::__construct(sprintf('"%s" is not a valid url!', $url)); } } diff --git a/src/FINDOLOGIC/Export/Exporter.php b/src/FINDOLOGIC/Export/Exporter.php index 6ef467e..71afc40 100644 --- a/src/FINDOLOGIC/Export/Exporter.php +++ b/src/FINDOLOGIC/Export/Exporter.php @@ -62,7 +62,7 @@ public static function create(int $type, int $itemsPerPage = 20, array $csvPrope /** * Turns the provided items into their serialized form. * - * @param array $items Array of items to serialize. All of them are serialized, regardless of $start and $total. + * @param Item[] $items Array of items to serialize. All of them are serialized, regardless of $start and $total. * @param int $start Assuming that $items is a fragment of the total, this is the global index of the first item in * $items. * @param int $count The number of items requested for this export step. Actual number of items can be smaller due @@ -79,7 +79,7 @@ abstract public function serializeItems(array $items, int $start, int $count, in * * @param string $targetDirectory The directory to which the file is written. The filename is at the exporter * implementation's discretion. - * @param array $items Array of items to serialize. All of them are serialized, regardless of $start and $total. + * @param Item[] $items Array of items to serialize. All of them are serialized, regardless of $start and $total. * @param int $start Assuming that $items is a fragment of the total, this is the global index of the first item in * $items. * @param int $count The number of items requested for this export step. Actual number of items can be smaller due diff --git a/src/FINDOLOGIC/Export/Helpers/DataHelper.php b/src/FINDOLOGIC/Export/Helpers/DataHelper.php index 87aacdc..2452bc4 100644 --- a/src/FINDOLOGIC/Export/Helpers/DataHelper.php +++ b/src/FINDOLOGIC/Export/Helpers/DataHelper.php @@ -68,7 +68,7 @@ public static function checkForEmptyValue(string $valueName, $value): string public static function validateUrl(string $url): string { if (!filter_var($url, FILTER_VALIDATE_URL) || !preg_match('/http[s]?:\/\/.*/', $url)) { - throw new InvalidUrlException(); + throw new InvalidUrlException($url); } return $url; diff --git a/src/FINDOLOGIC/Export/XML/Page.php b/src/FINDOLOGIC/Export/XML/Page.php index 215220a..c13da32 100644 --- a/src/FINDOLOGIC/Export/XML/Page.php +++ b/src/FINDOLOGIC/Export/XML/Page.php @@ -4,6 +4,7 @@ use DOMDocument; use FINDOLOGIC\Export\Constant; +use FINDOLOGIC\Export\Data\Item; use FINDOLOGIC\Export\Exceptions\ItemsExceedCountValueException; use FINDOLOGIC\Export\Exceptions\XMLSchemaViolationException; use FINDOLOGIC\Export\Helpers\XMLHelper; @@ -28,6 +29,9 @@ public function addItem(XMLItem $item): void array_push($this->items, $item); } + /** + * @param Item[] $items + */ public function setAllItems(array $items): void { $this->items = []; diff --git a/src/FINDOLOGIC/Export/XML/XMLItem.php b/src/FINDOLOGIC/Export/XML/XMLItem.php index df41ec6..5e86a00 100644 --- a/src/FINDOLOGIC/Export/XML/XMLItem.php +++ b/src/FINDOLOGIC/Export/XML/XMLItem.php @@ -164,7 +164,7 @@ private function buildUsergroups(DOMDocument $document): DOMElement /** * Checks if there is at least one image of type default * - * @param array $images The images to validate. + * @param Image[] $images The images to validate. * @return boolean Whether the images are valid or not. * @throws BaseImageMissingException */ diff --git a/tests/FINDOLOGIC/Export/Tests/CSVSerializationTest.php b/tests/FINDOLOGIC/Export/Tests/CSVSerializationTest.php index d19dc0d..428c3eb 100644 --- a/tests/FINDOLOGIC/Export/Tests/CSVSerializationTest.php +++ b/tests/FINDOLOGIC/Export/Tests/CSVSerializationTest.php @@ -35,6 +35,8 @@ class CSVSerializationTest extends TestCase private const DEFAULT_CSV_HEADING = "id\tordernumber\tname\tsummary\tdescription\tprice\tinstead\tmaxprice\t" . "taxrate\turl\timage\tattributes\tkeywords\tgroups\tbonus\tsales_frequency\tdate_added\tsort"; + private const CSV_PATH = '/tmp/findologic.csv'; + /** @var CSVExporter */ private $exporter; @@ -48,10 +50,9 @@ public function setUp(): void public function tearDown(): void { - try { - unlink('/tmp/findologic.csv'); - } catch (Exception $e) { - // No need to delete a written file if the test didn't write it. + if (file_exists(self::CSV_PATH)) { + // Cleanup file after tests have created it. + unlink(self::CSV_PATH); } } @@ -126,11 +127,50 @@ public function testHeadingIsOnlyWrittenForFirstPage(): void public function testCsvCanBeWrittenDirectlyToFile(): void { $item = $this->getMinimalItem(); + $expectedCsvContent = $this->exporter->serializeItems([$item], 0, 1, 1); + + $this->exporter->serializeItemsToFile('/tmp', [$item], 0, 1, 1); + + $this->assertEquals($expectedCsvContent, file_get_contents(self::CSV_PATH)); + $this->assertCount(2, file(self::CSV_PATH)); + } + public function testCsvWillNotOverrideItselfWhenHavingMultipleSteps(): void + { + $item = $this->getMinimalItem(); + $expectedCsvContent = $this->exporter->serializeItems([$item, $item], 0, 2, 2); + + $this->exporter->serializeItemsToFile('/tmp', [$item], 0, 1, 2); + $this->exporter->serializeItemsToFile('/tmp', [$item], 1, 1, 2); + + $this->assertEquals($expectedCsvContent, file_get_contents(self::CSV_PATH)); + $this->assertCount(3, file(self::CSV_PATH)); + } + + public function testCsvWillNotOverrideItselfWhenPassingACountHigherThenZero(): void + { + $item = $this->getMinimalItem(); + $expectedInitialData = 'This is some pretty nice data.'; + $expectedCsvContent = $this->exporter->serializeItems([$item], 1, 1, 1); + + file_put_contents(self::CSV_PATH, $expectedInitialData); + $this->exporter->serializeItemsToFile('/tmp', [$item], 1, 1, 1); + + $actualContents = file_get_contents(self::CSV_PATH); + $this->assertStringStartsWith($expectedInitialData, $actualContents); + $this->assertStringEndsWith($expectedCsvContent, $actualContents); + } + + public function testCsvWillOverrideItselfWhenPassingAnInitialCount(): void + { + $item = $this->getMinimalItem(); $expectedCsvContent = $this->exporter->serializeItems([$item], 0, 1, 1); + + file_put_contents(self::CSV_PATH, 'This is some pretty nice data.'); $this->exporter->serializeItemsToFile('/tmp', [$item], 0, 1, 1); - self::assertEquals($expectedCsvContent, file_get_contents('/tmp/findologic.csv')); + $this->assertEquals($expectedCsvContent, file_get_contents(self::CSV_PATH)); + $this->assertCount(2, file(self::CSV_PATH)); } public function testKitchenSink(): void diff --git a/tests/FINDOLOGIC/Export/Tests/XmlSerializationTest.php b/tests/FINDOLOGIC/Export/Tests/XmlSerializationTest.php index 8a6364d..6b37374 100644 --- a/tests/FINDOLOGIC/Export/Tests/XmlSerializationTest.php +++ b/tests/FINDOLOGIC/Export/Tests/XmlSerializationTest.php @@ -469,6 +469,15 @@ public function testAddingInvalidUrlToImageElementCausesException(): void $image->getDomSubtree(new DOMDocument()); } + public function testAddingImproperlyEncodedUrlToImageElementCausesException(): void + { + $this->expectException(InvalidUrlException::class); + $this->expectExceptionMessage('"https://store.com/Alu-Style-Ø-270-cm50324901e845e.jpg" is not a valid url!'); + + $image = new Image('https://store.com/Alu-Style-Ø-270-cm50324901e845e.jpg'); + $image->getDomSubtree(new DOMDocument()); + } + public function testAddingUrlsToXmlDomWorksAsExpected(): void { $item = $this->getMinimalItem();