Skip to content

Commit 071aa2b

Browse files
authored
Merge pull request #4 from LibreCodeCoop/feat/env-aware-integration-tests
test: add env-aware integration flow for sandbox and legacy PFX
2 parents 151d641 + 0bcc378 commit 071aa2b

File tree

5 files changed

+225
-0
lines changed

5 files changed

+225
-0
lines changed

.env.example

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
# SPDX-FileCopyrightText: 2026 LibreCode coop and contributors
2+
# SPDX-License-Identifier: AGPL-3.0-or-later
3+
4+
# Local sandbox setup for nfse-php smoke tests
5+
# Copy to .env.local and fill the secret values.
6+
7+
# Full HEAD URL for sandbox mTLS smoke test
8+
# Example:
9+
# NFSE_HEAD_URL="https://adn.producaorestrita.nfse.gov.br/dps/00000000000000000000000000000000000000000000"
10+
NFSE_HEAD_URL="https://adn.producaorestrita.nfse.gov.br/dps/CHANGE_ME"
11+
12+
# PFX certificate used for mTLS (local file, never commit)
13+
NFSE_MTLS_PFX_PATH=".secrets/pfx/2025-LibreCode.pfx"
14+
NFSE_MTLS_PFX_PASSWORD="CHANGE_ME"
15+
16+
# Optional: set to 1 to print TLS handshake details
17+
NFSE_CURL_VERBOSE=0
18+
19+
# Optional OpenBao/Vault settings (if needed by local app wiring)
20+
VAULT_ADDR="http://openbao:8200"
21+
VAULT_TOKEN=""
22+
VAULT_ROLE_ID=""
23+
VAULT_SECRET_ID=""

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,3 +3,6 @@
33
/.php-cs-fixer.cache
44
/.phpunit.cache
55
/composer.lock
6+
/.env
7+
/.env.local
8+
/.secrets/
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
<?php
2+
3+
// SPDX-FileCopyrightText: 2026 LibreCode coop and contributors
4+
// SPDX-License-Identifier: AGPL-3.0-or-later
5+
6+
declare(strict_types=1);
7+
8+
namespace LibreCodeCoop\NfsePHP\Tests\Integration\Http;
9+
10+
use LibreCodeCoop\NfsePHP\Tests\Support\LoadsLocalEnv;
11+
use LibreCodeCoop\NfsePHP\Tests\TestCase;
12+
13+
/**
14+
* Optional sandbox connectivity smoke test (mTLS).
15+
* Skips if env vars are not configured.
16+
*/
17+
class SandboxMtlsHeadTest extends TestCase
18+
{
19+
use LoadsLocalEnv;
20+
21+
public function testSandboxHeadWithMtlsWhenEnvIsPresent(): void
22+
{
23+
self::loadLocalEnv();
24+
25+
$url = getenv('NFSE_HEAD_URL') ?: '';
26+
$pfxPath = getenv('NFSE_MTLS_PFX_PATH') ?: '';
27+
$pfxPassword = getenv('NFSE_MTLS_PFX_PASSWORD') ?: '';
28+
29+
if ($url === '' || $pfxPath === '' || $pfxPassword === '') {
30+
self::markTestSkipped('Set NFSE_HEAD_URL, NFSE_MTLS_PFX_PATH and NFSE_MTLS_PFX_PASSWORD to run sandbox mTLS test.');
31+
}
32+
33+
if (!str_starts_with($pfxPath, '/')) {
34+
$pfxPath = dirname(__DIR__, 3) . '/' . ltrim($pfxPath, '/');
35+
}
36+
37+
if (!is_file($pfxPath)) {
38+
self::markTestSkipped('Configured PFX file does not exist for mTLS test.');
39+
}
40+
41+
$cmd = sprintf(
42+
'curl --silent --show-error --output /dev/null --write-out "%%{http_code}" --head --cert-type P12 --cert %s %s; echo "|exit:$?"',
43+
escapeshellarg($pfxPath . ':' . $pfxPassword),
44+
escapeshellarg($url)
45+
);
46+
47+
$result = shell_exec($cmd);
48+
49+
self::assertNotFalse($result, 'curl execution failed');
50+
51+
$result = trim((string) $result);
52+
53+
if (!str_contains($result, '|exit:')) {
54+
self::fail('Unexpected curl result format.');
55+
}
56+
57+
[$httpCode, $exitPart] = explode('|exit:', $result, 2);
58+
$httpCode = trim($httpCode);
59+
$exitCode = (int) trim($exitPart);
60+
61+
if ($exitCode !== 0) {
62+
self::markTestSkipped('mTLS curl failed in local runtime (likely OpenSSL/PFX compatibility).');
63+
}
64+
65+
self::assertContains($httpCode, ['200', '401', '403', '404']);
66+
}
67+
}
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
<?php
2+
3+
// SPDX-FileCopyrightText: 2026 LibreCode coop and contributors
4+
// SPDX-License-Identifier: AGPL-3.0-or-later
5+
6+
declare(strict_types=1);
7+
8+
namespace LibreCodeCoop\NfsePHP\Tests\Integration\Xml;
9+
10+
use LibreCodeCoop\NfsePHP\Exception\PfxImportException;
11+
use LibreCodeCoop\NfsePHP\SecretStore\NoOpSecretStore;
12+
use LibreCodeCoop\NfsePHP\Tests\Support\LoadsLocalEnv;
13+
use LibreCodeCoop\NfsePHP\Tests\TestCase;
14+
use LibreCodeCoop\NfsePHP\Xml\DpsSigner;
15+
16+
/**
17+
* Optional integration test:
18+
* - Uses real PFX when env vars are available
19+
* - Skips cleanly when env vars are absent
20+
*/
21+
class DpsSignerIntegrationTest extends TestCase
22+
{
23+
use LoadsLocalEnv;
24+
25+
public function testSignsXmlWithConfiguredPfxWhenEnvIsPresent(): void
26+
{
27+
self::loadLocalEnv();
28+
29+
$cnpj = getenv('NFS_TEST_CNPJ') ?: '11222333000181';
30+
$pfxPath = getenv('NFSE_MTLS_PFX_PATH') ?: '';
31+
$pfxPassword = getenv('NFSE_MTLS_PFX_PASSWORD') ?: '';
32+
33+
if ($pfxPath === '' || $pfxPassword === '') {
34+
self::markTestSkipped('Set NFSE_MTLS_PFX_PATH and NFSE_MTLS_PFX_PASSWORD to run real-PFX integration test.');
35+
}
36+
37+
if (!str_starts_with($pfxPath, '/')) {
38+
$pfxPath = dirname(__DIR__, 3) . '/' . ltrim($pfxPath, '/');
39+
}
40+
41+
if (!is_file($pfxPath)) {
42+
self::markTestSkipped('Configured PFX file does not exist for integration test.');
43+
}
44+
45+
$store = new NoOpSecretStore();
46+
$store->put('pfx/' . $cnpj, [
47+
'pfx_path' => $pfxPath,
48+
'password' => $pfxPassword,
49+
]);
50+
51+
$signer = new DpsSigner($store);
52+
$xml = '<DPS><infDPS Id="DPS123"><x>abc</x></infDPS></DPS>';
53+
54+
try {
55+
$signed = $signer->sign($xml, $cnpj);
56+
} catch (PfxImportException $e) {
57+
$message = strtolower($e->getMessage());
58+
59+
// Local OpenSSL runtime may not support legacy PKCS#12 algorithms.
60+
if (str_contains($message, 'digital envelope routines') || str_contains($message, 'asn1 encoding routines')) {
61+
self::markTestSkipped('Local OpenSSL runtime cannot import this PFX format.');
62+
}
63+
64+
throw $e;
65+
}
66+
67+
self::assertStringContainsString('<Signature', $signed);
68+
self::assertStringContainsString('DigestValue', $signed);
69+
}
70+
}

tests/Support/LoadsLocalEnv.php

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
<?php
2+
3+
// SPDX-FileCopyrightText: 2026 LibreCode coop and contributors
4+
// SPDX-License-Identifier: AGPL-3.0-or-later
5+
6+
declare(strict_types=1);
7+
8+
namespace LibreCodeCoop\NfsePHP\Tests\Support;
9+
10+
trait LoadsLocalEnv
11+
{
12+
private static bool $envLoaded = false;
13+
14+
protected static function loadLocalEnv(): void
15+
{
16+
if (self::$envLoaded) {
17+
return;
18+
}
19+
20+
self::$envLoaded = true;
21+
22+
$root = dirname(__DIR__, 2);
23+
24+
self::loadFile($root . '/.env.local');
25+
self::loadFile($root . '/.env');
26+
}
27+
28+
private static function loadFile(string $path): void
29+
{
30+
if (!is_file($path)) {
31+
return;
32+
}
33+
34+
$lines = file($path, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
35+
36+
if ($lines === false) {
37+
return;
38+
}
39+
40+
foreach ($lines as $line) {
41+
$line = trim($line);
42+
43+
if ($line === '' || str_starts_with($line, '#') || !str_contains($line, '=')) {
44+
continue;
45+
}
46+
47+
[$key, $value] = explode('=', $line, 2);
48+
49+
$key = trim($key);
50+
if ($key === '' || getenv($key) !== false) {
51+
continue;
52+
}
53+
54+
$value = trim($value);
55+
$value = trim($value, "\"'");
56+
57+
putenv($key . '=' . $value);
58+
$_ENV[$key] = $value;
59+
$_SERVER[$key] = $value;
60+
}
61+
}
62+
}

0 commit comments

Comments
 (0)