From b7c350544764d7a9c6773ec87a138e36c8a09c04 Mon Sep 17 00:00:00 2001 From: Carlos C Soto Date: Thu, 25 May 2023 08:19:26 -0600 Subject: [PATCH 1/8] Refactor RepositoryItem creaton to a factory --- docs/CHANGELOG.md | 7 ++++ tests/Integration/Repository.php | 4 +- tests/Integration/RepositoryItem.php | 22 ----------- tests/Integration/RepositoryItemFactory.php | 42 +++++++++++++++++++++ 4 files changed, 52 insertions(+), 23 deletions(-) create mode 100644 tests/Integration/RepositoryItemFactory.php diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index 3062533..b5205ff 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -6,6 +6,13 @@ Usamos [Versionado Semántico 2.0.0](SEMVER.md) por lo que puedes usar esta libr ## Cambios aún no liberados en una versión +### Cambios no liberados: 2023-05-25 + +Estos cambios se realizan en el entorno de desarrollo y pruebas, por lo que no es necesario hacer una liberación. + +- Se refactoriza la clase `RepositoryItem` para que las responsabilidades de la creación de una instancia + a partir de un arreglo se realizen en la clase `RepositoryItemFactory`. + ### Cambios no liberados: 2023-02-13 - Actualización de herramientas de desarrollo. diff --git a/tests/Integration/Repository.php b/tests/Integration/Repository.php index 39827db..bb331ec 100644 --- a/tests/Integration/Repository.php +++ b/tests/Integration/Repository.php @@ -50,12 +50,14 @@ public static function fromArray($dataItems): self if (! is_array($dataItems)) { throw new RuntimeException('JSON decoded contents from %s is not an array'); } + + $itemFactory = new RepositoryItemFactory(); $items = []; foreach ($dataItems as $index => $dataItem) { if (! is_array($dataItem)) { throw new RuntimeException("Entry $index is not an array"); } - $item = RepositoryItem::fromArray($dataItem); + $item = $itemFactory->make($dataItem); $items[$item->getUuid()] = $item; } return new self($items); diff --git a/tests/Integration/RepositoryItem.php b/tests/Integration/RepositoryItem.php index 9f7a86a..8a5ddd0 100644 --- a/tests/Integration/RepositoryItem.php +++ b/tests/Integration/RepositoryItem.php @@ -5,8 +5,6 @@ namespace PhpCfdi\CfdiSatScraper\Tests\Integration; use DateTimeImmutable; -use DomainException; -use Exception; use JsonSerializable; class RepositoryItem implements JsonSerializable @@ -31,26 +29,6 @@ public function __construct(string $uuid, DateTimeImmutable $date, string $state $this->state = strtoupper(substr($state, 0, 1)); } - /** @param array $item */ - public static function fromArray(array $item): self - { - return new self( - strval($item['uuid'] ?? ''), - self::dateFromString(strval($item['date'] ?? '')), - strval($item['state'] ?? ''), - strval($item['type'] ?? ''), - ); - } - - private static function dateFromString(string $value): DateTimeImmutable - { - try { - return new DateTimeImmutable($value); - } catch (Exception $exception) { - throw new DomainException(sprintf('Unable to parse date with value %s', $value)); - } - } - public function getUuid(): string { return $this->uuid; diff --git a/tests/Integration/RepositoryItemFactory.php b/tests/Integration/RepositoryItemFactory.php new file mode 100644 index 0000000..d28e9e5 --- /dev/null +++ b/tests/Integration/RepositoryItemFactory.php @@ -0,0 +1,42 @@ + $values */ + public function make(array $values): RepositoryItem + { + $maker = new self(); + return new RepositoryItem( + $maker->stringFromValues($values, 'uuid'), + $maker->dateFromString($maker->stringFromValues($values, 'date')), + $maker->stringFromValues($values, 'state'), + $maker->stringFromValues($values, 'type'), + ); + } + + /** @param array $values */ + private function stringFromValues(array $values, string $key): string + { + if (! isset($values[$key]) || ! is_string($values[$key])) { + throw new DomainException(sprintf('Cannot create an entry with invalid %s', $key)); + } + return $values[$key]; + } + + private function dateFromString(string $value): DateTimeImmutable + { + try { + return new DateTimeImmutable($value); + } catch (Exception $exception) { + throw new DomainException(sprintf('Unable to parse date with value %s', $value)); + } + } +} From 62f674f8f4b04a1e12828e42de7ee4261052931a Mon Sep 17 00:00:00 2001 From: Carlos C Soto Date: Thu, 25 May 2023 08:27:42 -0600 Subject: [PATCH 2/8] Fix PHPStan false positive detecting cast from object to string --- docs/CHANGELOG.md | 1 + src/Exceptions/ResourceDownloadError.php | 6 ++++++ 2 files changed, 7 insertions(+) diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index b5205ff..2f9b1e5 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -12,6 +12,7 @@ Estos cambios se realizan en el entorno de desarrollo y pruebas, por lo que no e - Se refactoriza la clase `RepositoryItem` para que las responsabilidades de la creación de una instancia a partir de un arreglo se realizen en la clase `RepositoryItemFactory`. +- Se corrige el issue falso positivo encontrado por PHPStan al convertir un objeto a cadena de caracteres. ### Cambios no liberados: 2023-02-13 diff --git a/src/Exceptions/ResourceDownloadError.php b/src/Exceptions/ResourceDownloadError.php index df7a008..35d8147 100644 --- a/src/Exceptions/ResourceDownloadError.php +++ b/src/Exceptions/ResourceDownloadError.php @@ -4,6 +4,7 @@ namespace PhpCfdi\CfdiSatScraper\Exceptions; +use Stringable; use Throwable; /** @@ -67,6 +68,11 @@ public static function reasonToString($reason): string return strval($reason); } if (is_object($reason) && is_callable([$reason, '__toString'])) { + /** + * Fix PHPStan false positive detecting cast from object to string + * @phpstan-var Stringable $reason + * @noinspection PhpMultipleClassDeclarationsInspection + */ return strval($reason); } return print_r($reason, true); From 93cae67c99cb1624ca14f2aa284cd71299aeb052 Mon Sep 17 00:00:00 2001 From: Carlos C Soto Date: Thu, 25 May 2023 08:27:53 -0600 Subject: [PATCH 3/8] Update development tools --- .phive/phars.xml | 8 ++++---- docs/CHANGELOG.md | 1 + 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/.phive/phars.xml b/.phive/phars.xml index 5fb4fa9..23b1725 100644 --- a/.phive/phars.xml +++ b/.phive/phars.xml @@ -1,7 +1,7 @@ - - - - + + + + diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index 2f9b1e5..cc8b8d6 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -13,6 +13,7 @@ Estos cambios se realizan en el entorno de desarrollo y pruebas, por lo que no e - Se refactoriza la clase `RepositoryItem` para que las responsabilidades de la creación de una instancia a partir de un arreglo se realizen en la clase `RepositoryItemFactory`. - Se corrige el issue falso positivo encontrado por PHPStan al convertir un objeto a cadena de caracteres. +- Actualización de herramientas de desarrollo. ### Cambios no liberados: 2023-02-13 From 7ce37d84886fd18444e2977c38abdb10438237a4 Mon Sep 17 00:00:00 2001 From: Carlos C Soto Date: Thu, 25 May 2023 11:03:47 -0600 Subject: [PATCH 4/8] Code style --- src/ResourceDownloader.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ResourceDownloader.php b/src/ResourceDownloader.php index e058ba3..cbf2a06 100644 --- a/src/ResourceDownloader.php +++ b/src/ResourceDownloader.php @@ -210,7 +210,7 @@ protected function makePromises(): Traversable * @throws RuntimeException if ask to create folder path exists and is not a folder * @throws RuntimeException if unable to create folder * - * @see \PhpCfdi\CfdiSatScraper\Internal\ResourceDownloadStoreInFolder + * @see ResourceDownloadStoreInFolder */ public function saveTo(string $destinationFolder, bool $createFolder = false, int $createMode = 0775): array { From 3bda33803817f17fc3457f51c0e7049cdd35e237 Mon Sep 17 00:00:00 2001 From: Carlos C Soto Date: Thu, 25 May 2023 11:05:11 -0600 Subject: [PATCH 5/8] Refactor test and fix file existence checks --- tests/Integration/RetrieveByUuidTest.php | 59 ++++++++++++++++++------ 1 file changed, 44 insertions(+), 15 deletions(-) diff --git a/tests/Integration/RetrieveByUuidTest.php b/tests/Integration/RetrieveByUuidTest.php index d4571c4..422f556 100644 --- a/tests/Integration/RetrieveByUuidTest.php +++ b/tests/Integration/RetrieveByUuidTest.php @@ -10,7 +10,10 @@ namespace PhpCfdi\CfdiSatScraper\Tests\Integration; use PhpCfdi\CfdiSatScraper\Filters\DownloadType; +use PhpCfdi\CfdiSatScraper\MetadataList; use PhpCfdi\CfdiSatScraper\ResourceType; +use PhpCfdi\CfdiSatScraper\SatScraper; +use RuntimeException; class RetrieveByUuidTest extends IntegrationTestCase { @@ -18,10 +21,10 @@ class RetrieveByUuidTest extends IntegrationTestCase public function providerRetrieveByUuid(): array { return [ - 'recibidos, random 1' => [DownloadType::recibidos(), 8], + 'recibidos, random 1' => [DownloadType::recibidos(), 1], 'emitidos, random 1' => [DownloadType::emitidos(), 1], - 'recibidos, random 4' => [DownloadType::recibidos(), 3], - 'emitidos, random 4' => [DownloadType::emitidos(), 3], + 'recibidos, random 10' => [DownloadType::recibidos(), 10], + 'emitidos, random 10' => [DownloadType::emitidos(), 10], ]; } @@ -32,6 +35,8 @@ public function providerRetrieveByUuid(): array */ public function testRetrieveByUuid(DownloadType $downloadType, int $count): void { + // set up + $resourceType = ResourceType::xml(); $typeText = $this->getDownloadTypeText($downloadType); $repository = $this->getRepository()->filterByType($downloadType); $repository = $repository->randomize()->topItems($count); @@ -43,6 +48,7 @@ public function testRetrieveByUuid(DownloadType $downloadType, int $count): void ); } + // check that all uuids exists and don't have more $scraper = $this->getSatScraper(); $list = $scraper->listByUuids($uuids, $downloadType); foreach ($uuids as $uuid) { @@ -50,22 +56,45 @@ public function testRetrieveByUuid(DownloadType $downloadType, int $count): void } $this->assertCount(count($uuids), $list, sprintf('It was expected to receive only %d records', count($uuids))); - $tempDir = sys_get_temp_dir(); - foreach ($uuids as $uuid) { - $filename = strtolower(sprintf('%s/%s.xml', $tempDir, $uuid)); - if (file_exists($filename)) { - unlink($filename); - } - } - $scraper->resourceDownloader(ResourceType::xml(), $list)->saveTo($tempDir); + // just use items that have a download link + $list = $list->filterWithResourceLink($resourceType); + + // clean destination + $tempDir = sys_get_temp_dir() . '/cfdi-sat-scraper/retrieve-by-uuid'; + shell_exec(sprintf('rm -rf %s', escapeshellarg($tempDir))); + shell_exec(sprintf('mkdir -p %s', escapeshellarg($tempDir))); + + // perform download + $this->downloadWithRetry($scraper, $resourceType, $list, $tempDir); + + // check file existence foreach ($repository->getIterator() as $uuid => $item) { - $filename = strtolower(sprintf('%s/%s.xml', $tempDir, $uuid)); - if ('Cancelado' !== $item->getState()) { - $this->assertFileDoesNotExist($filename, sprintf('The cfdi file with uuid %s does not exists: %s', $uuid, $filename)); + $filename = sprintf('%s/%s.xml', $tempDir, strtolower($uuid)); + if (! $list->has($uuid)) { + $this->assertFileDoesNotExist($filename, sprintf('The cfdi file with uuid %s should not exists: %s', $uuid, $filename)); } else { - $this->assertFileExists($filename, sprintf('The cfdi file with uuid %s does not exists: %s', $uuid, $filename)); + $this->assertFileExists($filename, sprintf('The cfdi file with uuid %s should exists: %s', $uuid, $filename)); $this->assertCfdiHasUuid($uuid, file_get_contents($filename) ?: ''); } } } + + private function downloadWithRetry(SatScraper $scraper, ResourceType $resourceType, MetadataList $list, string $destination): void + { + $maxAttempts = 10; + $attempt = 1; + $downloader = $scraper->resourceDownloader($resourceType); + + $list = $list->filterWithResourceLink($resourceType); + while ($list->count() > 0) { + $downloader->setMetadataList($list); + $downloaded = $downloader->saveTo($destination); + $list = $list->filterWithOutUuids($downloaded); + $attempt = $attempt + 1; + if ($attempt === $maxAttempts) { + print_r(['Missing' => $list]); + throw new RuntimeException(sprintf('Unable to domplete download after %s attempts', $attempt)); + } + } + } } From 1d9b99655fece14736d396017346a3f6a63486a7 Mon Sep 17 00:00:00 2001 From: Carlos C Soto Date: Thu, 25 May 2023 11:14:23 -0600 Subject: [PATCH 6/8] Upgrade to guzzlehttp/promises:^2.0 --- composer.json | 2 +- docs/CHANGELOG.md | 10 ++++++++-- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/composer.json b/composer.json index 77c7a19..9a348bd 100644 --- a/composer.json +++ b/composer.json @@ -43,7 +43,7 @@ "ext-openssl": "*", "psr/http-message": "^1.0", "guzzlehttp/guzzle": "^7.0", - "guzzlehttp/promises": "^1.3", + "guzzlehttp/promises": "^2.0", "symfony/dom-crawler": "^5.4|^6.0", "symfony/css-selector": "^5.4|^6.0", "eclipxe/enum": "^0.2.0", diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index cc8b8d6..10d57d6 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -6,15 +6,21 @@ Usamos [Versionado Semántico 2.0.0](SEMVER.md) por lo que puedes usar esta libr ## Cambios aún no liberados en una versión -### Cambios no liberados: 2023-05-25 +En este momento no hay cambios no liberados. -Estos cambios se realizan en el entorno de desarrollo y pruebas, por lo que no es necesario hacer una liberación. +## Versión 3.2.3 2023-05-25 + +- Se actualiza la dependencia de `guzzlehttp/promises` a versión 2.0. + +Los siguientes cambios aplican al entorno de desarrollo: - Se refactoriza la clase `RepositoryItem` para que las responsabilidades de la creación de una instancia a partir de un arreglo se realizen en la clase `RepositoryItemFactory`. - Se corrige el issue falso positivo encontrado por PHPStan al convertir un objeto a cadena de caracteres. - Actualización de herramientas de desarrollo. +También se concluyen los siguientes cambios previos no liberados. + ### Cambios no liberados: 2023-02-13 - Actualización de herramientas de desarrollo. From 1a3a29592815e0414aa4ad3300f29d1cc2375487 Mon Sep 17 00:00:00 2001 From: Carlos C Soto Date: Thu, 25 May 2023 12:09:01 -0600 Subject: [PATCH 7/8] Upgrade psr/http-message to ^1.1 or ^2.0 This change require phpcfdi/image-captcha-resolver version ^0.2.3 --- composer.json | 4 ++-- docs/CHANGELOG.md | 5 ++++- tests/Unit/Exceptions/ResourceDownloadResponseErrorTest.php | 2 +- 3 files changed, 7 insertions(+), 4 deletions(-) diff --git a/composer.json b/composer.json index 9a348bd..a91087c 100644 --- a/composer.json +++ b/composer.json @@ -41,7 +41,7 @@ "ext-fileinfo": "*", "ext-mbstring": "*", "ext-openssl": "*", - "psr/http-message": "^1.0", + "psr/http-message": "^1.1|^2.0", "guzzlehttp/guzzle": "^7.0", "guzzlehttp/promises": "^2.0", "symfony/dom-crawler": "^5.4|^6.0", @@ -49,7 +49,7 @@ "eclipxe/enum": "^0.2.0", "eclipxe/micro-catalog": "^v0.1.3", "phpcfdi/credentials": "^1.1", - "phpcfdi/image-captcha-resolver": "^0.2.0" + "phpcfdi/image-captcha-resolver": "^0.2.3" }, "require-dev": { "ext-iconv": "*", diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index 10d57d6..4aecb39 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -10,12 +10,15 @@ En este momento no hay cambios no liberados. ## Versión 3.2.3 2023-05-25 -- Se actualiza la dependencia de `guzzlehttp/promises` a versión 2.0. +- Se actualiza la dependencia de `guzzlehttp/promises` a versión mínima 2.0. +- Se actualiza la dependencia de `psr/http-message` a versiones mínimas 1.1 o 2.0. +- Se actualiza la dependencia de `phpcfdi/image-captcha-resolver` a versión mínima 0.2.3. Los siguientes cambios aplican al entorno de desarrollo: - Se refactoriza la clase `RepositoryItem` para que las responsabilidades de la creación de una instancia a partir de un arreglo se realizen en la clase `RepositoryItemFactory`. +- Se corrigen las pruebas para usar `psr/http-message:^2.0`. - Se corrige el issue falso positivo encontrado por PHPStan al convertir un objeto a cadena de caracteres. - Actualización de herramientas de desarrollo. diff --git a/tests/Unit/Exceptions/ResourceDownloadResponseErrorTest.php b/tests/Unit/Exceptions/ResourceDownloadResponseErrorTest.php index 6e7661e..194d00d 100644 --- a/tests/Unit/Exceptions/ResourceDownloadResponseErrorTest.php +++ b/tests/Unit/Exceptions/ResourceDownloadResponseErrorTest.php @@ -23,7 +23,7 @@ public function testInvalidStatusCode(): void { /** @var ResponseInterface&MockObject $response */ $response = $this->createMock(ResponseInterface::class); - $response->method('getStatusCode')->willReturn('503'); + $response->method('getStatusCode')->willReturn(503); $uuid = 'uuid'; $exception = ResourceDownloadResponseError::invalidStatusCode($response, $uuid); $this->assertSame( From f4abcb8081c8bff4f286ec2ef2ae8e2abf728c0b Mon Sep 17 00:00:00 2001 From: Carlos C Soto Date: Thu, 25 May 2023 12:10:26 -0600 Subject: [PATCH 8/8] Run php-cs-fixer fix on PHP >= 8.0 --- composer.json | 4 ++-- docs/CHANGELOG.md | 1 + 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/composer.json b/composer.json index a91087c..39b4b5c 100644 --- a/composer.json +++ b/composer.json @@ -63,11 +63,11 @@ "@dev:tests" ], "dev:check-style": [ - "@php tools/php-cs-fixer fix --dry-run --verbose", + "@php -r 'exit(intval(PHP_VERSION_ID >= 74000));' || $PHP_BINARY tools/php-cs-fixer fix --dry-run --verbose", "@php tools/phpcs --colors -sp" ], "dev:fix-style": [ - "@php tools/php-cs-fixer fix --verbose", + "@php -r 'exit(intval(PHP_VERSION_ID >= 74000));' || $PHP_BINARY tools/php-cs-fixer fix --verbose", "@php tools/phpcbf --colors -sp" ], "dev:tests": [ diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index 4aecb39..4b70149 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -16,6 +16,7 @@ En este momento no hay cambios no liberados. Los siguientes cambios aplican al entorno de desarrollo: +- La ejecución de `php-cs-fixer` dentro de `composer` se condiciona a mínimo PHP 8.0. - Se refactoriza la clase `RepositoryItem` para que las responsabilidades de la creación de una instancia a partir de un arreglo se realizen en la clase `RepositoryItemFactory`. - Se corrigen las pruebas para usar `psr/http-message:^2.0`.