Skip to content

Commit 5ed0ea3

Browse files
authored
Merge pull request #91 from eclipxe13/improve-tests
Mejorar la cobertura de código en la ejecución de SonarQube Cloud
2 parents 9447080 + a2e1138 commit 5ed0ea3

15 files changed

+443
-4
lines changed

.github/workflows/build.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -121,4 +121,4 @@ jobs:
121121
- name: Install project dependencies
122122
run: composer upgrade --no-interaction --no-progress --prefer-dist
123123
- name: Tests (phpunit)
124-
run: vendor/bin/phpunit --testdox
124+
run: vendor/bin/phpunit --testdox --display-all-issues

.github/workflows/sonarqube-cloud.yml

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,8 +41,27 @@ jobs:
4141
restore-keys: ${{ runner.os }}-composer-
4242
- name: Install project dependencies
4343
run: composer upgrade --no-interaction --no-progress --prefer-dist
44+
- name: Restore encrypted files
45+
run: |
46+
bash tests/secure-files decrypt \
47+
tests/_files/secure/environment \
48+
tests/_files/secure/certificate.cer \
49+
tests/_files/secure/private.key \
50+
tests/_files/secure/private.pass \
51+
tests/_files/secure/repository.json
52+
mv tests/_files/secure/environment tests/.env
53+
mv tests/_files/secure/repository.json tests/repository.json
54+
env:
55+
ENCFILESKEY: ${{ secrets.ENCFILESKEY }}
4456
- name: Create code coverage
45-
run: vendor/bin/phpunit --testdox --coverage-xml=build/coverage --coverage-clover=build/coverage/clover.xml --log-junit=build/coverage/junit.xml
57+
run: vendor/bin/phpunit --testsuite=complete --display-all-issues --coverage-xml=build/coverage --coverage-clover=build/coverage/clover.xml --log-junit=build/coverage/junit.xml
58+
- name: Clean up sensitive information
59+
run: |
60+
rm -f tests/_files/secure/certificate.cer \
61+
tests/_files/secure/private.key \
62+
tests/_files/secure/private.pass \
63+
tests/.env \
64+
tests/repository.json
4665
- name: Prepare SonarCloud Code Coverage Files
4766
run: |
4867
sed 's#'$GITHUB_WORKSPACE'#/github/workspace#g' build/coverage/junit.xml > build/sonar-junit.xml

composer.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,7 @@
8282
],
8383
"dev:tests": [
8484
"@dev:check-style",
85-
"@php vendor/bin/phpunit --testdox",
85+
"@php vendor/bin/phpunit --testdox --display-all-issues",
8686
"@php tools/phpstan analyze --no-progress --verbose"
8787
]
8888
},

develop/TestIntegracion.md

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,3 +65,25 @@ php vendor/bin/phpunit --testsuite integration
6565
# overriding SAT_AUTH_MODE (use CIEC or FIEL)
6666
SAT_AUTH_MODE="FIEL" php vendor/bin/phpunit --testsuite integration
6767
```
68+
69+
## Integración con los flujos de trabajo de GitHub
70+
71+
Se ha configurado el proyecto para proveer de forma segura una FIEL, una clave CIEC y un entorno de pruebas seguro.
72+
73+
Los archivos involucrados son:
74+
75+
- `tests/_files/secure/environment`, que se reubica en `tests/.env`.
76+
- `tests/_files/secure/certificate.cer`
77+
- `tests/_files/secure/private.key`
78+
- `tests/_files/secure/private.pass`
79+
- `tests/_files/secure/repository.json`, que se reubica en `tests/repository.json`.
80+
81+
Para encriptar y desencriptar los archivos se usa el *script* `tests/` que a su vez usa `gpg`.
82+
Esta herramienta requiere de la variable de entorno `ENCFILESKEY`, que se establece en los
83+
flujos de trabajo de GitHub usando los secretos del repositorio.
84+
85+
Solo GitHub tiene la llave de encriptación. En 2025-09-30 fue usada para encriptar los archivos, almacenada y olvidada.
86+
87+
Si estos archivos requieren cambiar -ya sea porque se generó un mejor repositorio, se están especificando nuevas
88+
credenciales CIEC o se ha cambiado la FIEL- es necesario volver a encriptar los archivos con una nueva clave,
89+
almacenar la clave en los secretos de GITHUB e incluir los nuevos archivos en el repositorio.

docs/CHANGELOG.md

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,13 @@ Usamos [Versionado Semántico 2.0.0](SEMVER.md) por lo que puedes usar esta libr
66

77
## Cambios aún no liberados en una versión
88

9-
Ninguno.
9+
## Pruebas 2025-09-30
10+
11+
- Se mejoran las pruebas unitarias.
12+
- Se agrega una FIEL, datos de CIEC y un repositorio comparable al entorno de pruebas de SonarCube,
13+
con el propósito de incrementar sustancialmente la cobertura de código.
14+
- La FIEL, datos de CIEC y repositorio se encuentra encriptada y solo GitHub puede desencriptarla.
15+
- La clave de encriptación está en un secreto el repositorio llamado `ENCFILESKEY`.
1016

1117
## Versión 5.0.1 2025-09-26
1218

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace PhpCfdi\CfdiSatScraper\Tests\Unit\Internal;
6+
7+
use Exception;
8+
use PhpCfdi\CfdiSatScraper\Contracts\ResourceDownloadHandlerInterface;
9+
use PhpCfdi\CfdiSatScraper\Exceptions\ResourceDownloadError;
10+
use Psr\Http\Message\ResponseInterface;
11+
12+
final class ResourceDownloadHandlerSpy implements ResourceDownloadHandlerInterface
13+
{
14+
public string $lastUuid;
15+
16+
public string $lastContent;
17+
18+
public ResponseInterface $lastResponse;
19+
20+
public ResourceDownloadError $lastError;
21+
22+
public Exception|null $onSuccessException = null;
23+
24+
public function onSuccess(string $uuid, string $content, ResponseInterface $response): void
25+
{
26+
if (null !== $this->onSuccessException) {
27+
throw $this->onSuccessException;
28+
}
29+
$this->lastUuid = $uuid;
30+
$this->lastContent = $content;
31+
$this->lastResponse = $response;
32+
}
33+
34+
public function onError(ResourceDownloadError $error): void
35+
{
36+
$this->lastError = $error;
37+
}
38+
}
Lines changed: 158 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,158 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace PhpCfdi\CfdiSatScraper\Tests\Unit\Internal;
6+
7+
use Exception;
8+
use GuzzleHttp\Exception\RequestException;
9+
use GuzzleHttp\Psr7\Response;
10+
use PhpCfdi\CfdiSatScraper\Contracts\ResourceDownloadHandlerInterface;
11+
use PhpCfdi\CfdiSatScraper\Exceptions\ResourceDownloadResponseError;
12+
use PhpCfdi\CfdiSatScraper\Internal\ResourceDownloaderPromiseHandler;
13+
use PhpCfdi\CfdiSatScraper\ResourceType;
14+
use PhpCfdi\CfdiSatScraper\Tests\TestCase;
15+
use RuntimeException;
16+
17+
final class ResourceDownloaderPromiseHandlerTest extends TestCase
18+
{
19+
public function testValidateResponseWithInvalidStatusCode(): void
20+
{
21+
$resourceDownloadHandler = $this->createMock(ResourceDownloadHandlerInterface::class);
22+
$response = new Response(400);
23+
$uuid = 'b01017a7-e1b8-4a25-9e31-85ab56526f54';
24+
25+
$handler = new ResourceDownloaderPromiseHandler(ResourceType::xml(), $resourceDownloadHandler);
26+
27+
$this->expectException(ResourceDownloadResponseError::class);
28+
$this->expectExceptionMessage(
29+
sprintf('Download of CFDI %s return an invalid status code %d', $uuid, $response->getStatusCode()),
30+
);
31+
32+
$handler->validateResponse($response, $uuid);
33+
}
34+
35+
public function testValidateResponseWithEmptyBody(): void
36+
{
37+
$resourceDownloadHandler = $this->createMock(ResourceDownloadHandlerInterface::class);
38+
$response = new Response(200, body: '');
39+
$uuid = 'b01017a7-e1b8-4a25-9e31-85ab56526f54';
40+
41+
$handler = new ResourceDownloaderPromiseHandler(ResourceType::xml(), $resourceDownloadHandler);
42+
43+
$this->expectException(ResourceDownloadResponseError::class);
44+
$this->expectExceptionMessage(
45+
sprintf('Download of CFDI %s return an empty body', $uuid),
46+
);
47+
48+
$handler->validateResponse($response, $uuid);
49+
}
50+
51+
public function testValidateResponseIsXmlButWithoutUuid(): void
52+
{
53+
$resourceDownloadHandler = $this->createMock(ResourceDownloadHandlerInterface::class);
54+
$response = new Response(200, body: '<root/>');
55+
$uuid = 'b01017a7-e1b8-4a25-9e31-85ab56526f54';
56+
57+
$handler = new ResourceDownloaderPromiseHandler(ResourceType::xml(), $resourceDownloadHandler);
58+
59+
$this->expectException(ResourceDownloadResponseError::class);
60+
$this->expectExceptionMessage(
61+
sprintf('Download of CFDI %s return something that is not a CFDI', $uuid),
62+
);
63+
64+
$handler->validateResponse($response, $uuid);
65+
}
66+
67+
public function testValidateResponseIsPdfButMimeTypeDoesNotMatch(): void
68+
{
69+
$resourceDownloadHandler = $this->createMock(ResourceDownloadHandlerInterface::class);
70+
$mimeType = 'text/plain';
71+
$response = new Response(200, body: 'text');
72+
$uuid = 'b01017a7-e1b8-4a25-9e31-85ab56526f54';
73+
74+
$handler = new ResourceDownloaderPromiseHandler(ResourceType::pdf(), $resourceDownloadHandler);
75+
76+
$this->expectException(ResourceDownloadResponseError::class);
77+
$this->expectExceptionMessage(
78+
sprintf('Download of CFDI %s return something that is not a PDF (mime %s)', $uuid, $mimeType),
79+
);
80+
81+
$handler->validateResponse($response, $uuid);
82+
}
83+
84+
public function testPromiseFullfilledWithValidResponse(): void
85+
{
86+
$resourceDownloadHandler = new ResourceDownloadHandlerSpy();
87+
$handler = new ResourceDownloaderPromiseHandler(ResourceType::xml(), $resourceDownloadHandler);
88+
$uuid = 'b01017a7-e1b8-4a25-9e31-85ab56526f54';
89+
$cfdiContent = <<< XML
90+
<?xml version="1.0" encoding="UTF-8"?>
91+
<cfdi:Comprobante xmlns:cfdi="http://www.sat.gob.mx/cfd/4">
92+
<tfd:TimbreFiscalDigital xmlns:tfd="http://www.sat.gob.mx/TimbreFiscalDigital" UUID="$uuid"/>
93+
</cfdi:Comprobante>
94+
XML;
95+
$response = new Response(200, body: $cfdiContent);
96+
$handler->promiseFulfilled($response, $uuid);
97+
98+
$this->assertSame($uuid, $resourceDownloadHandler->lastUuid);
99+
$this->assertSame($cfdiContent, $resourceDownloadHandler->lastContent);
100+
$this->assertSame($response, $resourceDownloadHandler->lastResponse);
101+
$this->assertSame([$uuid], $handler->downloadedUuids());
102+
}
103+
104+
public function testPromiseFullfilledWithErrorResponse(): void
105+
{
106+
$resourceDownloadHandler = new ResourceDownloadHandlerSpy();
107+
$handler = new ResourceDownloaderPromiseHandler(ResourceType::xml(), $resourceDownloadHandler);
108+
$uuid = 'b01017a7-e1b8-4a25-9e31-85ab56526f54';
109+
$response = new Response(500);
110+
$handler->promiseFulfilled($response, $uuid);
111+
112+
$this->assertSame($uuid, $resourceDownloadHandler->lastError->getUuid());
113+
}
114+
115+
public function testPromiseFullfilledWithException(): void
116+
{
117+
$resourceDownloadHandler = new ResourceDownloadHandlerSpy();
118+
$resourceDownloadHandler->onSuccessException = new Exception('Dummy exception');
119+
$handler = new ResourceDownloaderPromiseHandler(ResourceType::xml(), $resourceDownloadHandler);
120+
$uuid = 'b01017a7-e1b8-4a25-9e31-85ab56526f54';
121+
$cfdiContent = <<< XML
122+
<?xml version="1.0" encoding="UTF-8"?>
123+
<cfdi:Comprobante xmlns:cfdi="http://www.sat.gob.mx/cfd/4">
124+
<tfd:TimbreFiscalDigital xmlns:tfd="http://www.sat.gob.mx/TimbreFiscalDigital" UUID="$uuid"/>
125+
</cfdi:Comprobante>
126+
XML;
127+
$response = new Response(200, body: $cfdiContent);
128+
$handler->promiseFulfilled($response, $uuid);
129+
130+
$this->assertSame($uuid, $resourceDownloadHandler->lastError->getUuid());
131+
}
132+
133+
public function testPromiseRejected(): void
134+
{
135+
$resourceDownloadHandler = new ResourceDownloadHandlerSpy();
136+
$handler = new ResourceDownloaderPromiseHandler(ResourceType::xml(), $resourceDownloadHandler);
137+
$reason = $this->createMock(RuntimeException::class);
138+
$uuid = 'b01017a7-e1b8-4a25-9e31-85ab56526f54';
139+
$handler->promiseRejected($reason, $uuid);
140+
141+
$handledError = $resourceDownloadHandler->lastError;
142+
$this->assertSame($uuid, $handledError->getUuid());
143+
$this->assertSame($reason, $handledError->getReason());
144+
}
145+
146+
public function testPromiseRejectedReasonIsRequestException(): void
147+
{
148+
$resourceDownloadHandler = new ResourceDownloadHandlerSpy();
149+
$handler = new ResourceDownloaderPromiseHandler(ResourceType::xml(), $resourceDownloadHandler);
150+
$reason = $this->createMock(RequestException::class);
151+
$uuid = 'b01017a7-e1b8-4a25-9e31-85ab56526f54';
152+
$handler->promiseRejected($reason, $uuid);
153+
154+
$handledError = $resourceDownloadHandler->lastError;
155+
$this->assertSame($uuid, $handledError->getUuid());
156+
$this->assertSame($reason, $handledError->getReason());
157+
}
158+
}
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace PhpCfdi\CfdiSatScraper\Tests\Unit\Internal;
6+
7+
use PhpCfdi\CfdiSatScraper\Internal\ResourceFileNamerByType;
8+
use PhpCfdi\CfdiSatScraper\ResourceType;
9+
use PhpCfdi\CfdiSatScraper\Tests\TestCase;
10+
11+
final class ResourceFileNamerByTypeTest extends TestCase
12+
{
13+
public function testNameForByResourceTypeXml(): void
14+
{
15+
$type = ResourceType::xml();
16+
$uuid = 'b4fd0e43-4145-413b-a0fb-f6ce9b617e3c';
17+
$namer = new ResourceFileNamerByType($type);
18+
$this->assertSame("$uuid.xml", $namer->nameFor($uuid));
19+
}
20+
21+
public function testNameForByResourceTypePdf(): void
22+
{
23+
$type = ResourceType::pdf();
24+
$uuid = 'b4fd0e43-4145-413b-a0fb-f6ce9b617e3c';
25+
$namer = new ResourceFileNamerByType($type);
26+
$this->assertSame("$uuid.pdf", $namer->nameFor($uuid));
27+
}
28+
29+
public function testNameForByResourceTypeCancelRequest(): void
30+
{
31+
$type = ResourceType::cancelRequest();
32+
$uuid = 'b4fd0e43-4145-413b-a0fb-f6ce9b617e3c';
33+
$namer = new ResourceFileNamerByType($type);
34+
$this->assertSame("$uuid-cancel-request.pdf", $namer->nameFor($uuid));
35+
}
36+
37+
public function testNameForByResourceTypeCancelVoucher(): void
38+
{
39+
$type = ResourceType::cancelVoucher();
40+
$uuid = 'b4fd0e43-4145-413b-a0fb-f6ce9b617e3c';
41+
$namer = new ResourceFileNamerByType($type);
42+
$this->assertSame("$uuid-cancel-voucher.pdf", $namer->nameFor($uuid));
43+
}
44+
}

tests/Unit/QueryByUuidTest.php

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace PhpCfdi\CfdiSatScraper\Tests\Unit;
6+
7+
use PhpCfdi\CfdiSatScraper\Exceptions\InvalidArgumentException;
8+
use PhpCfdi\CfdiSatScraper\Filters\Options\UuidOption;
9+
use PhpCfdi\CfdiSatScraper\QueryByUuid;
10+
use PhpCfdi\CfdiSatScraper\Tests\TestCase;
11+
12+
final class QueryByUuidTest extends TestCase
13+
{
14+
public function testQueryByUuidPropertyUuid(): void
15+
{
16+
$initialUuid = new UuidOption('df71f5a5-134c-4c94-ab87-0476436a9a5e');
17+
$query = new QueryByUuid($initialUuid);
18+
$this->assertSame($initialUuid, $query->getUuid());
19+
20+
$changedUuid = new UuidOption('5062dbee-2ccc-48a9-98cf-550c9aeb7466');
21+
$returnAfterSet = $query->setUuid($changedUuid);
22+
$this->assertSame($changedUuid, $query->getUuid());
23+
$this->assertSame($query, $returnAfterSet);
24+
}
25+
26+
public function testQueryByUuidConstructWithEmptyUuidThrowsException(): void
27+
{
28+
$emptyUuid = new UuidOption('');
29+
$this->expectException(InvalidArgumentException::class);
30+
$this->expectExceptionMessage('UUID');
31+
32+
new QueryByUuid($emptyUuid);
33+
}
34+
35+
public function testQueryByUuidSetEmptyUuidThrowsException(): void
36+
{
37+
$initialUuid = new UuidOption('df71f5a5-134c-4c94-ab87-0476436a9a5e');
38+
$query = new QueryByUuid($initialUuid);
39+
$emptyUuid = new UuidOption('');
40+
41+
$this->expectException(InvalidArgumentException::class);
42+
$this->expectExceptionMessage('UUID');
43+
44+
$query->setUuid($emptyUuid);
45+
}
46+
}
1.47 KB
Binary file not shown.

0 commit comments

Comments
 (0)