feat: portability safeguards — signed goldens, HTTP contract suite, committed OpenAPI (Task 6)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
jonatanritter
2026-07-22 18:25:08 -03:00
co-authored by Claude Opus 4.8
parent 3aae8b67ef
commit fb2b8ce372
13 changed files with 2688 additions and 0 deletions
+55
View File
@@ -0,0 +1,55 @@
"""Task 6: one-shot generator for `tests/goldens_signed/*.expected.xml` --
NOT run automatically by the test suite. Run manually whenever a golden case
in the `sowai-fiscal` lib is added/changed (or `tests/fixtures/test_cert.pfx`
is rotated), then commit the regenerated files.
Signs each `sowai_fiscal.goldens/*.input.json` case's `DadosEmissao` (loaded
via `sowai_fiscal.golden_helpers.dados_from_json` -- SAME helper the lib's
own pre-signature goldens use, so `chave_acesso`/`numero`/`cnf`/`dh_emi` are
whatever that JSON already pins, unchanged) with the FIXED test certificate.
Deterministic because RSA PKCS#1v1.5 signing has no random padding: the SAME
(input XML, private key) pair always produces the SAME signature bytes --
this is the byte-exact assertion `tests/test_signed_goldens.py` checks.
uv run python scripts/generate_signed_goldens.py
"""
import importlib.resources
from pathlib import Path
from cryptography.hazmat.primitives.serialization import pkcs12
from sowai_fiscal.golden_helpers import dados_from_json
from sowai_fiscal.xml_builder import build_nfe
from fiscal_svc.emission.service import _serialize_nfe, sign_nfe_xml
REPO_DIR = Path(__file__).resolve().parents[1]
CERT_PATH = REPO_DIR / "tests" / "fixtures" / "test_cert.pfx"
CERT_PASSWORD = "test-cert-password" # must match generate_test_cert.py
OUT_DIR = REPO_DIR / "tests" / "goldens_signed"
def main() -> None:
if not CERT_PATH.exists():
raise SystemExit(
f"{CERT_PATH} não existe -- rode scripts/generate_test_cert.py primeiro"
)
pfx_bytes = CERT_PATH.read_bytes()
private_key, cert, _ca_certs = pkcs12.load_key_and_certificates(
pfx_bytes, CERT_PASSWORD.encode("utf-8")
)
OUT_DIR.mkdir(parents=True, exist_ok=True)
goldens_dir = Path(str(importlib.resources.files("sowai_fiscal") / "goldens"))
for input_path in sorted(goldens_dir.glob("*.input.json")):
case = input_path.name.removesuffix(".input.json")
dados = dados_from_json(input_path)
nfe = build_nfe(dados)
xml_str = _serialize_nfe(nfe)
xml_assinado = sign_nfe_xml(xml_str, dados.chave_acesso, private_key, cert)
out_path = OUT_DIR / f"{case}.expected.xml"
out_path.write_text(xml_assinado, encoding="utf-8")
print(f"wrote {out_path}")
if __name__ == "__main__":
main()
+67
View File
@@ -0,0 +1,67 @@
"""Task 6: one-shot generator for `tests/fixtures/test_cert.pfx` -- a
self-signed TEST-ONLY A1 certificate, NEVER a real one (design spec's "os 3
seguros de portabilidade", item (b)/(c) extended by the F2 emenda: goldens
ASSINADOS need a FIXED test certificate, committed as a binary fixture).
Re-running this script produces a DIFFERENT RSA key pair (key generation is
not seeded) -- that is fine and expected: this is meant to be run ONCE and
the output COMMITTED. The determinism `tests/test_signed_goldens.py` relies
on comes from every test run reusing the SAME committed `.pfx` file (so the
same private key signs the same canonicalized XML the same way every time,
RSA PKCS#1v1.5 having no random padding) -- NOT from this generator being
reproducible bit-for-bit across runs. Re-run only if the fixture needs to be
rotated, and regenerate `tests/goldens_signed/*.expected.xml`
(`scripts/generate_signed_goldens.py`) together with it, in the same commit.
uv run python scripts/generate_test_cert.py
"""
from datetime import datetime, timedelta, timezone
from pathlib import Path
from cryptography import x509
from cryptography.hazmat.primitives import hashes, serialization
from cryptography.hazmat.primitives.asymmetric import rsa
from cryptography.hazmat.primitives.serialization import pkcs12
from cryptography.x509.oid import NameOID
# Mesmo CNPJ do emitente usado em TODOS os casos golden da lib
# (`sowai_fiscal.goldens/*.input.json`) -- ver esse pacote se algum caso
# novo usar um CNPJ diferente; este script/fixture precisaria acompanhar.
CNPJ = "12345678000190"
PASSWORD = "test-cert-password"
OUT_PATH = Path(__file__).resolve().parents[1] / "tests" / "fixtures" / "test_cert.pfx"
def main() -> None:
key = rsa.generate_private_key(public_exponent=65537, key_size=2048)
subject = issuer = x509.Name(
[
x509.NameAttribute(NameOID.COMMON_NAME, f"AUTOPECAS THIAGO LTDA:{CNPJ}"),
x509.NameAttribute(NameOID.SERIAL_NUMBER, CNPJ),
]
)
now = datetime.now(timezone.utc)
cert = (
x509.CertificateBuilder()
.subject_name(subject)
.issuer_name(issuer)
.public_key(key.public_key())
.serial_number(x509.random_serial_number())
.not_valid_before(now - timedelta(days=1))
.not_valid_after(now + timedelta(days=3650))
.sign(key, hashes.SHA256())
)
pfx_bytes = pkcs12.serialize_key_and_certificates(
name=b"sowai-fiscal-svc-test",
key=key,
cert=cert,
cas=None,
encryption_algorithm=serialization.BestAvailableEncryption(PASSWORD.encode("utf-8")),
)
OUT_PATH.parent.mkdir(parents=True, exist_ok=True)
OUT_PATH.write_bytes(pfx_bytes)
print(f"wrote {OUT_PATH} (cnpj={CNPJ}, password={PASSWORD!r})")
if __name__ == "__main__":
main()