From bfd7ebf9b1386da05b93e6dc805ff942513d9e13 Mon Sep 17 00:00:00 2001 From: Carlos C Soto Date: Tue, 15 Oct 2024 10:07:35 -0600 Subject: [PATCH] Refactor App to allow retry - Add debug argument - Create data class to store arguments - Create buider class to parse arguments - Change static method `run` to regular --- .github/workflows/system.yml | 4 +- README.md | 3 +- bin/sat-pys-scraper | 13 +- docs/CHANGELOG.md | 15 ++ src/App/Arguments.php | 18 +++ src/App/ArgumentsBuilder.php | 98 ++++++++++++ src/App/SatPysScraper.php | 156 ++++++++----------- tests/Unit/App/ArgumentsBuilderTest.php | 188 +++++++++++++++++++++++ tests/Unit/App/SatPysScraperTest.php | 191 +++++------------------- 9 files changed, 423 insertions(+), 263 deletions(-) create mode 100644 src/App/Arguments.php create mode 100644 src/App/ArgumentsBuilder.php create mode 100644 tests/Unit/App/ArgumentsBuilderTest.php diff --git a/.github/workflows/system.yml b/.github/workflows/system.yml index d6d01bc..1e98597 100644 --- a/.github/workflows/system.yml +++ b/.github/workflows/system.yml @@ -43,6 +43,4 @@ jobs: - name: Install project dependencies run: composer upgrade --no-interaction --no-progress --prefer-dist --no-dev - name: System test with PHP ${{ matrix.php-version }} - run: php bin/sat-pys-scraper --json build/result.json --xml build/result.xml --sort key - env: - MAX_TRIES: 5 + run: php bin/sat-pys-scraper --tries 5 --json build/result.json --xml build/result.xml --sort key diff --git a/README.md b/README.md index 7a573fa..ce2a67c 100644 --- a/README.md +++ b/README.md @@ -173,7 +173,8 @@ sin temor a romper tu aplicación. |---------|----------|-----------------------------------| | 1.0.0 | 8.2, 8.3 | 2023-12-13 Fuera de mantenimiento | | 2.0.0 | 8.2, 8.3 | 2024-03-07 Fuera de mantenimiento | -| 3.0.0 | 8.2, 8.3 | 2024-03-07 | +| 3.0.0 | 8.2, 8.3 | 2024-03-07 Fuera de mantenimiento | +| 4.0.0 | 8.2, 8.3 | 2024-10-17 | ## Contribuciones diff --git a/bin/sat-pys-scraper b/bin/sat-pys-scraper index 5b96f1f..f127395 100644 --- a/bin/sat-pys-scraper +++ b/bin/sat-pys-scraper @@ -7,15 +7,4 @@ use PhpCfdi\SatPysScraper\App\SatPysScraper; require __DIR__ . '/../vendor/autoload.php'; -exit(call_user_func(function (string ...$argv): int { - $result = 1; - $maxTries = ($_SERVER['MAX_TRIES'] ?? null) ?? ($_ENV['MAX_TRIES'] ?? null); - $maxTries = is_numeric($maxTries) ? intval($maxTries) : 1; - for ($try = 0; $try < $maxTries; $try++) { - $result = SatPysScraper::run($argv); - if (0 === $result) { - break; - } - } - return $result; -}, ...$argv)); +exit((new SatPysScraper())->run($argv)); diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index 15e3a47..24d573d 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -11,6 +11,21 @@ versión, aunque sí su incorporación en la rama principal de trabajo. Generalm ## Listado de cambios +### Versión 4.0.0 2024-10-17 + +Esta es una actualización de refactorización que obliga a crear una versión mayor. +Si no utilizas entidades del espacio de nombres `PhpCfdi\SatPysScraper\App` entonces puedes hacer el cambio +de la versión `3.x` a la versión `4.x` sin conflictos. En caso contrario debes revisar tu implementación. + +- Se agrega el parámetro `--debug` que, si existe, vuelca los datos del error de ejecución. +- Se agrega el parámetro `--tries` que, si existe, reintenta la descarga de información hasta ese número de veces. +- Se extrae el procesamiento de argumentos a su propia clase. +- Se extrae el almacenamiento de argumentos a su propia clase en lugar de un arreglo. +- Se reorganizan las pruebas de acuerdo a los cambios previos. +- La ejecución del flujo de trabajo `system.yml` en el trabajo `system-tests` se configura con `--tries 5`. +- Se vuelve a simplificar la herramienta `bin/sat-pys-scraper` para que toda su lógica esté probada. +- Ya no se usa la variable de entorno `MAX_TRIES`. + ### Versión 3.0.2 2024-10-17 A la herramienta `bin/sat-pys-scraper` se le puede definir un número máximo de ejecuciones en la diff --git a/src/App/Arguments.php b/src/App/Arguments.php new file mode 100644 index 0000000..5085cad --- /dev/null +++ b/src/App/Arguments.php @@ -0,0 +1,18 @@ + $this->setXml((string) array_shift($arguments)), + in_array($argument, ['--json', '-j'], true) => $this->setJson((string) array_shift($arguments)), + in_array($argument, ['--sort', '-s'], true) => $this->setSort((string) array_shift($arguments)), + in_array($argument, ['--tries', '-t'], true) => $this->setTries((string) array_shift($arguments)), + in_array($argument, ['--quiet', '-q'], true) => $this->setQuiet(), + in_array($argument, ['--debug', '-d'], true) => $this->setDebug(), + default => throw new ArgumentException(sprintf('Invalid argument "%s"', $argument)), + }; + } + + if ('' === $this->xml && '' === $this->json) { + throw new ArgumentException('Did not specify --xml or --json arguments'); + } + if ('-' === $this->xml && '-' === $this->json) { + throw new ArgumentException('Cannot send --xml and --json result to standard output at the same time'); + } + + return new Arguments( + xml: '-' === $this->xml ? 'php://stdout' : $this->xml, + json: '-' === $this->json ? 'php://stdout' : $this->json, + sort: $this->sort, + tries: $this->tries, + quiet: $this->quiet, + debug: $this->debug, + ); + } + + private function setXml(string $argument): void + { + $this->xml = $argument; + if ('-' === $argument) { + $this->quiet = true; + } + } + + private function setJson(string $argument): void + { + $this->json = $argument; + if ('-' === $argument) { + $this->quiet = true; + } + } + + /** @throws ArgumentException */ + private function setSort(string $argument): void + { + if (! in_array($argument, ['key', 'name'], true)) { + throw new ArgumentException(sprintf('Invalid sort "%s"', $argument)); + } + $this->sort = $argument; + } + + /** @throws ArgumentException */ + private function setTries(string $argument): void + { + $this->tries = (int) $argument; + if ((string) $this->tries !== $argument || $this->tries < 1) { + throw new ArgumentException(sprintf('Invalid tries "%s"', $argument)); + } + } + + private function setQuiet(): void + { + $this->quiet = true; + } + + private function setDebug(): void + { + $this->debug = true; + } +} diff --git a/src/App/SatPysScraper.php b/src/App/SatPysScraper.php index 3c6c9c3..8f1f4f9 100644 --- a/src/App/SatPysScraper.php +++ b/src/App/SatPysScraper.php @@ -6,6 +6,8 @@ use GuzzleHttp\Client; use PhpCfdi\SatPysScraper\Data\Types; +use PhpCfdi\SatPysScraper\Exceptions\HttpException; +use PhpCfdi\SatPysScraper\Exceptions\HttpServerException; use PhpCfdi\SatPysScraper\Generator; use PhpCfdi\SatPysScraper\NullGeneratorTracker; use PhpCfdi\SatPysScraper\Scraper; @@ -15,40 +17,14 @@ final readonly class SatPysScraper { - /** - * @param list $arguments - */ - public function __construct(private string $command, private array $arguments, private ScraperInterface $scraper) + public function printHelp(string $command): void { - } - - /** @param string[] $argv */ - public static function run( - array $argv, - ScraperInterface $scraper = new Scraper(new Client()), - string $stdErrFile = 'php://stderr' - ): int { - $command = (string) array_shift($argv); - $argv = array_values($argv); - $app = new self($command, $argv, $scraper); - try { - $app->execute(); - return 0; - } catch (Throwable $exception) { - file_put_contents($stdErrFile, 'ERROR: ' . $exception->getMessage() . PHP_EOL, FILE_APPEND); - return 1; - } - } - - public function printHelp(): void - { - $command = basename($this->command); echo <<< HELP $command - Crea un archivo XML con la clasificación de productos y servicios del SAT. Sintaxis: $command help|-h|--help - $command [--quiet|-q] [--json|-j JSON_FILE] [--xml|-x XML_FILE] + $command [--quiet|-q] [--debug|-d] [--json|-j JSON_FILE] [--xml|-x XML_FILE] [--tries|-t TRIES] Argumentos: --xml|-x XML_FILE @@ -59,12 +35,18 @@ public function printHelp(): void los datos generados en formato JSON. --sort|-s SORT Establece el orden de elementos, default: key, se puede usar "key" o "name". + --tries|-t TRIES + Establece cuántas veces debe intentar hacer la descarga si encuentra un error de servidor. + Default: 1. El valor debe ser mayor o igual a 1. + --debug|-d + Mensajes de intentos e información del error se envían a la salida estándar de error. --quiet|-q Modo de operación silencioso. Notas: Debe especificar al menos un argumento "--xml" o "--json", o ambos. No se puede especificar "-" como salida de "--xml" y "--json" al mismo tiempo. + Al especificar la salida "-" se activa automáticamente el modo silencioso. Acerca de: Este script pertenece al proyecto https://github.com/phpcfdi/sat-pys-scraper @@ -74,84 +56,66 @@ public function printHelp(): void HELP; } - /** @throws ArgumentException */ - public function execute(): void + /** @param list $argv */ + public function run(array $argv, ScraperInterface|null $scraper = null, string $stdErrFile = 'php://stderr'): int { - if ([] !== array_intersect($this->arguments, ['help', '-h', '--help'])) { - $this->printHelp(); - return; - } - - $arguments = $this->processArguments(...$this->arguments); - $tracker = ($arguments['quiet']) ? new NullGeneratorTracker() : new PrinterGeneratorTracker(); - $types = (new Generator($this->scraper, $tracker))->generate(); - - // sort types - match ($arguments['sort']) { - 'key' => $types->sortByKey(), - 'name' => $types->sortByName(), - default => throw new ArgumentException('Unrecognized sort argument'), - }; + $command = (string) array_shift($argv); + $app = new self(); - if ('' !== $arguments['xml']) { - $this->toXml($arguments['xml'], $types); - } - if ('' !== $arguments['json']) { - $this->toJson($arguments['json'], $types); + if ([] !== array_intersect($argv, ['help', '-h', '--help'])) { + $app->printHelp(basename($command)); + return 0; } - } + $debug = [] !== array_intersect($argv, ['-d', '--debug']); + $try = 0; - /** - * @return array{xml: string, json: string, quiet: bool, sort: string} - * @throws ArgumentException - */ - public function processArguments(string ...$arguments): array - { - $arguments = array_values($arguments); - $xml = ''; - $json = ''; - $quiet = false; - $sort = 'key'; - - while ([] !== $arguments) { - $argument = (string) array_shift($arguments); - if (in_array($argument, ['--xml', '-x'], true)) { - $xml = (string) array_shift($arguments); - } elseif (in_array($argument, ['--json', '-j'], true)) { - $json = (string) array_shift($arguments); - } elseif (in_array($argument, ['--sort', '-s'], true)) { - $sort = (string) array_shift($arguments); - if (! in_array($sort, ['key', 'name'])) { - throw new ArgumentException(sprintf('Invalid sort "%s"', $sort)); + try { + $arguments = (new ArgumentsBuilder())->build(...$argv); + $debug = $arguments->debug; + do { + $try = $try + 1; + try { + $app->execute($arguments, $scraper); + $serverException = null; + break; + } catch (HttpServerException $exception) { + $serverException = $exception; + usleep(1000); } - } elseif (in_array($argument, ['--quiet', '-q'], true)) { - $quiet = true; - } else { - throw new ArgumentException(sprintf('Invalid argument "%s"', $argument)); + } while ($try < $arguments->tries); + if (null !== $serverException) { + throw $serverException; } + } catch (Throwable $exception) { + file_put_contents($stdErrFile, 'ERROR: ' . $exception->getMessage() . PHP_EOL, FILE_APPEND); + if ($debug) { + file_put_contents($stdErrFile, "The procedure was executed $try times\n", FILE_APPEND); + file_put_contents($stdErrFile, print_r($exception, true), FILE_APPEND); + } + return 1; } + return 0; + } - if ('' === $xml && '' === $json) { - throw new ArgumentException('Did not specify --xml or --json arguments'); - } - if ('-' === $xml && '-' === $json) { - throw new ArgumentException('Cannot send --xml and --json result to standard output at the same time'); - } - if ('-' === $xml) { - $xml = 'php://stdout'; - $quiet = true; + /** @throws HttpServerException|HttpException */ + private function execute(Arguments $arguments, ScraperInterface|null $scraper): void + { + $tracker = ($arguments->quiet) ? new NullGeneratorTracker() : new PrinterGeneratorTracker(); + $scraper ??= new Scraper(new Client()); + $types = (new Generator($scraper, $tracker))->generate(); + + // sort types + match ($arguments->sort) { + 'name' => $types->sortByName(), + default => $types->sortByKey(), + }; + + if ('' !== $arguments->xml) { + $this->toXml($arguments->xml, $types); } - if ('-' === $json) { - $json = 'php://stdout'; - $quiet = true; + if ('' !== $arguments->json) { + $this->toJson($arguments->json, $types); } - - return [ - 'xml' => $xml, - 'json' => $json, - 'quiet' => $quiet, - 'sort' => $sort, - ]; } public function toXml(string $output, Types $types): void diff --git a/tests/Unit/App/ArgumentsBuilderTest.php b/tests/Unit/App/ArgumentsBuilderTest.php new file mode 100644 index 0000000..636e8c7 --- /dev/null +++ b/tests/Unit/App/ArgumentsBuilderTest.php @@ -0,0 +1,188 @@ +build(...$arguments); + + $this->assertSame('php://stdout', $result->xml); + $this->assertTrue($result->quiet); + } + + public function testXmlOutputToFile(): void + { + $outputFile = '/tmp/result.xml'; + $arguments = ['-x', $outputFile]; + $builder = new ArgumentsBuilder(); + + $result = $builder->build(...$arguments); + + $this->assertSame($outputFile, $result->xml); + $this->assertFalse($result->quiet); + } + + public function testJsonOutputToStandard(): void + { + $arguments = ['--json', '-']; + $builder = new ArgumentsBuilder(); + + $result = $builder->build(...$arguments); + + $this->assertSame('php://stdout', $result->json); + $this->assertTrue($result->quiet); + } + + public function testJsonOutputToFile(): void + { + $outputFile = '/tmp/result.xml'; + $arguments = ['-j', $outputFile]; + $builder = new ArgumentsBuilder(); + + $result = $builder->build(...$arguments); + + $this->assertSame($outputFile, $result->json); + $this->assertFalse($result->quiet); + } + + #[TestWith(['--quiet'])] + #[TestWith(['-q'])] + public function testQuiet(string $quiet): void + { + $arguments = ['-j', '/tmp/output', $quiet]; + $builder = new ArgumentsBuilder(); + + $result = $builder->build(...$arguments); + + $this->assertTrue($result->quiet); + } + + #[TestWith(['--debug'])] + #[TestWith(['-d'])] + public function testDebug(string $debug): void + { + $arguments = ['-j', '/tmp/output', $debug]; + $builder = new ArgumentsBuilder(); + + $result = $builder->build(...$arguments); + + $this->assertTrue($result->debug); + } + + public function testSetAll(): void + { + $arguments = [ + '--xml', 'result.xml', + '--json', 'result.json', + '--sort', 'name', + '--tries', '5', + '--quiet', + '--debug', + ]; + $builder = new ArgumentsBuilder(); + + $result = $builder->build(...$arguments); + + $this->assertSame('result.xml', $result->xml); + $this->assertSame('result.json', $result->json); + $this->assertSame('name', $result->sort); + $this->assertSame(5, $result->tries); + $this->assertTrue($result->quiet); + $this->assertTrue($result->debug); + } + + public function testSetMinimal(): void + { + $arguments = ['--xml', 'output']; + $builder = new ArgumentsBuilder(); + + $result = $builder->build(...$arguments); + + $this->assertSame('output', $result->xml); + $this->assertSame('', $result->json); + $this->assertSame('key', $result->sort); + $this->assertSame(1, $result->tries); + $this->assertFalse($result->quiet); + $this->assertFalse($result->debug); + } + + public function testEmpty(): void + { + $arguments = []; + $builder = new ArgumentsBuilder(); + + $this->expectException(ArgumentException::class); + $this->expectExceptionMessage('Did not specify --xml or --json arguments'); + $builder->build(...$arguments); + } + + public function testWithXmlAndJsonOutputToStdout(): void + { + $arguments = ['-x', '-', '-j', '-']; + $builder = new ArgumentsBuilder(); + + $this->expectException(ArgumentException::class); + $this->expectExceptionMessage('Cannot send --xml and --json result to standard output at the same time'); + $builder->build(...$arguments); + } + + public function testWithExtra(): void + { + $arguments = ['extra-argument']; + $builder = new ArgumentsBuilder(); + + $this->expectException(ArgumentException::class); + $this->expectExceptionMessage('Invalid argument "extra-argument"'); + $builder->build(...$arguments); + } + + public function testWithInvalidSort(): void + { + $arguments = ['--sort', 'foo']; + $builder = new ArgumentsBuilder(); + + $this->expectException(ArgumentException::class); + $this->expectExceptionMessage('Invalid sort "foo"'); + $builder->build(...$arguments); + } + + #[TestWith(['0'])] + #[TestWith(['-1'])] + #[TestWith(['not integer'])] + #[TestWith([''])] + public function testWithInvalidTries(string $tries): void + { + $arguments = ['--tries', $tries]; + $builder = new ArgumentsBuilder(); + + $this->expectException(ArgumentException::class); + $this->expectExceptionMessage(sprintf('Invalid tries "%s"', $tries)); + $builder->build(...$arguments); + } + + #[TestWith(['--xml'])] + #[TestWith(['--json'])] + public function testWithoutOutput(string $format): void + { + $arguments = [$format, '']; + $builder = new ArgumentsBuilder(); + + $this->expectException(ArgumentException::class); + $this->expectExceptionMessage('Did not specify --xml or --json arguments'); + $builder->build(...$arguments); + } +} diff --git a/tests/Unit/App/SatPysScraperTest.php b/tests/Unit/App/SatPysScraperTest.php index 8e16b8f..7cb4fd7 100644 --- a/tests/Unit/App/SatPysScraperTest.php +++ b/tests/Unit/App/SatPysScraperTest.php @@ -1,167 +1,29 @@ createMock(ScraperInterface::class); - $script = new SatPysScraper('command', $arguments, $scraper); - - $result = $script->processArguments(...$arguments); - - $this->assertSame([ - 'xml' => 'php://stdout', - 'json' => '', - 'quiet' => true, - 'sort' => 'key', - ], $result); - } - - public function testProcessXmlOutputToFile(): void - { - $outputFile = '/tmp/result.xml'; - $arguments = ['-x', $outputFile]; - $scraper = $this->createMock(ScraperInterface::class); - $script = new SatPysScraper('command', $arguments, $scraper); - - $result = $script->processArguments(...$arguments); - - $this->assertSame([ - 'xml' => $outputFile, - 'json' => '', - 'quiet' => false, - 'sort' => 'key', - ], $result); - } - - public function testProcessJsonOutputToFile(): void - { - $outputFile = '/tmp/result.xml'; - $arguments = ['-j', $outputFile]; - $scraper = $this->createMock(ScraperInterface::class); - $script = new SatPysScraper('command', $arguments, $scraper); - - $result = $script->processArguments(...$arguments); - - $this->assertSame([ - 'xml' => '', - 'json' => $outputFile, - 'quiet' => false, - 'sort' => 'key', - ], $result); - } - - public function testProcessJsonOutputToStandard(): void - { - $arguments = ['--json', '-']; - $scraper = $this->createMock(ScraperInterface::class); - $script = new SatPysScraper('command', $arguments, $scraper); - - $result = $script->processArguments(...$arguments); - - $this->assertSame([ - 'xml' => '', - 'json' => 'php://stdout', - 'quiet' => true, - 'sort' => 'key', - ], $result); - } - - public function testProcessXmlArgumentsSetAll(): void - { - $arguments = ['--xml', 'result.xml', '--json', 'result.json', '--sort', 'name', '--quiet']; - $scraper = $this->createMock(ScraperInterface::class); - $script = new SatPysScraper('command', $arguments, $scraper); - - $result = $script->processArguments(...$arguments); - - $this->assertSame([ - 'xml' => 'result.xml', - 'json' => 'result.json', - 'quiet' => true, - 'sort' => 'name', - ], $result); - } - - public function testProcessWithoutArguments(): void - { - $arguments = []; - $scraper = $this->createMock(ScraperInterface::class); - $script = new SatPysScraper('command', $arguments, $scraper); - - $this->expectException(ArgumentException::class); - $this->expectExceptionMessage('Did not specify --xml or --json arguments'); - $script->processArguments(...$arguments); - } - - public function testProcessWithXmlAndJsonOutputToStdout(): void - { - $arguments = ['-x', '-', '-j', '-']; - $scraper = $this->createMock(ScraperInterface::class); - $script = new SatPysScraper('command', $arguments, $scraper); - - $this->expectException(ArgumentException::class); - $this->expectExceptionMessage('Cannot send --xml and --json result to standard output at the same time'); - $script->processArguments(...$arguments); - } - - public function testProcessArgumentsWithExtra(): void - { - $arguments = ['extra-argument']; - $scraper = $this->createMock(ScraperInterface::class); - $script = new SatPysScraper('command', $arguments, $scraper); - - $this->expectException(ArgumentException::class); - $this->expectExceptionMessage('Invalid argument "extra-argument"'); - $script->processArguments(...$arguments); - } - - public function testProcessArgumentsWithInvalidSort(): void - { - $arguments = ['--sort', 'foo']; - $scraper = $this->createMock(ScraperInterface::class); - $script = new SatPysScraper('command', $arguments, $scraper); - - $this->expectException(ArgumentException::class); - $this->expectExceptionMessage('Invalid sort "foo"'); - $script->processArguments(...$arguments); - } - - #[TestWith(['--xml'])] - #[TestWith(['--json'])] - public function testProcessArgumentsWithoutOutput(string $format): void - { - $arguments = [$format, '']; - $scraper = $this->createMock(ScraperInterface::class); - $script = new SatPysScraper('command', $arguments, $scraper); - - $this->expectException(ArgumentException::class); - $this->expectExceptionMessage('Did not specify --xml or --json arguments'); - $script->processArguments(...$arguments); - } - #[TestWith(['--help'])] #[TestWith(['-h'])] #[TestWith(['help'])] public function testHelp(string $helpArgument): void { - $arguments = ['--first', $helpArgument, 'last']; - $scraper = $this->createMock(ScraperInterface::class); - $script = new SatPysScraper('command', $arguments, $scraper); + $arguments = ['command', '--first', $helpArgument, 'last']; + $script = new SatPysScraper(); $this->expectOutputRegex('/Crea un archivo XML con la clasificación de productos y servicios del SAT/'); - $script->execute(); + $script->run($arguments); } public function testRunWithPreparedScraper(): void @@ -174,7 +36,7 @@ public function testRunWithPreparedScraper(): void $jsonOutputFile = $this->createTemporaryFilename(); $argv = ['command', '--xml', $xmlOutputFile, '--json', $jsonOutputFile, '--quiet']; - $script = new SatPysScraper('command', $argv, $scraper); + $script = new SatPysScraper(); $this->expectOutputString(''); $result = $script->run(argv: $argv, scraper: $scraper); @@ -182,22 +44,49 @@ public function testRunWithPreparedScraper(): void $this->assertXmlFileEqualsXmlFile($expectedXmlFile, $xmlOutputFile); $this->assertJsonFileEqualsJsonFile($expectedJsonFile, $jsonOutputFile); $this->assertSame(0, $result); + + unlink($xmlOutputFile); + unlink($jsonOutputFile); } public function testRunWithError(): void { - $scraper = $this->createFakeScraper(); - $argv = ['command']; - $script = new SatPysScraper('command', $argv, $scraper); + $argv = ['command', '--debug']; + $script = new SatPysScraper(); $stdErrFile = $this->createTemporaryFilename(); - $result = $script->run(argv: $argv, scraper: $scraper, stdErrFile: $stdErrFile); + $result = $script->run(argv: $argv, stdErrFile: $stdErrFile); + $stdError = (string) file_get_contents($stdErrFile); + unlink($stdErrFile); $this->assertSame(1, $result); $this->assertStringContainsString( 'ERROR: Did not specify --xml or --json arguments', - (string) file_get_contents($stdErrFile), + $stdError, 'Expected error was not raised' ); } + + public function testRunWithTriesAndClientServerError(): void + { + // if tries is 2 then a different debug message is shown + // if tries is 4 then error is: Mock queue is empty + $argv = ['command', '--xml', '-', '--tries', '3', '--debug']; + $script = new SatPysScraper(); + $scraper = $this->createPreparedScraperQueue([ + new Response(500, reason: 'Internal Server Error'), + new Response(500, reason: 'Internal Server Error'), + new Response(500, reason: 'Internal Server Error'), + ]); + $stdErrFile = $this->createTemporaryFilename(); + + $result = $script->run(argv: $argv, scraper: $scraper, stdErrFile: $stdErrFile); + $stdError = (string) file_get_contents($stdErrFile); + unlink($stdErrFile); + + $this->assertSame(1, $result); + $this->assertStringContainsString('ERROR: Server error', $stdError, 'Expected error was not raised'); + $this->assertStringContainsString('The procedure was executed 3 times', $stdError); + $this->assertStringContainsString(ServerException::class, $stdError, 'Class was not printed'); + } }