"""Task 5: `POST /v1/emissoes` + `GET /v1/documentos/{id}[/xml]` -- ported from the auto's `tests/modules/fiscal/test_emissao.py`, with the porte table's biggest structural change applied: this service receives a COMPLETE `EmissaoRequest` (no Sale/Branch/Person/Part collection), so the "caminho feliz" setup here is FAR shorter -- upload a certificate + create a series + POST a golden's `DadosEmissao` payload, no cadastro at all. Golden cases (`sowai_fiscal.goldens/*.input.json`) are the payload SOURCE for the happy path -- proves the service round-trips a real, lib-shaped `DadosEmissao` end to end. Byte-exact signature determinism against pre-generated `.expected.xml` fixtures is Task 6's `test_signed_goldens.py`; this file only proves the document is well-formed/valid and the invariants (outbox, idempotency, `ver_proc` obrigatório) hold.""" import importlib.resources import json import uuid from datetime import datetime, timedelta, timezone from decimal import Decimal from pathlib import Path import pytest from cryptography import x509 from cryptography.fernet import Fernet 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 from httpx import ASGITransport, AsyncClient from lxml import etree from sqlalchemy import select from fiscal_svc.certificates import crypto as certificate_lib from fiscal_svc.core.db import get_session from fiscal_svc.documents.models import FiscalDocument, FiscalSeries from fiscal_svc.emission import service as emission_service from fiscal_svc.emission.schemas import EmissaoRequest from fiscal_svc.main import app from fiscal_svc.tenancy.service import create_product _GOLDENS_DIR = Path(str(importlib.resources.files("sowai_fiscal") / "goldens")) _CNPJ_EMITENTE = "12345678000190" @pytest.fixture(autouse=True) def _override_db(db_session): async def _get_session_override(): yield db_session app.dependency_overrides[get_session] = _get_session_override yield app.dependency_overrides.clear() @pytest.fixture(autouse=True) def _fiscal_cert_encryption_key(monkeypatch): monkeypatch.setenv("FISCAL_CERT_ENCRYPTION_KEY", Fernet.generate_key().decode("ascii")) certificate_lib._get_fernet.cache_clear() yield certificate_lib._get_fernet.cache_clear() def _build_test_pfx(cnpj: str = _CNPJ_EMITENTE, password: str = "senha123") -> bytes: 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=365)) .sign(key, hashes.SHA256()) ) return pkcs12.serialize_key_and_certificates( name=b"test", key=key, cert=cert, cas=None, encryption_algorithm=serialization.BestAvailableEncryption(password.encode("utf-8")), ) def _headers(api_key: str) -> dict[str, str]: return {"X-Api-Key": api_key} async def _product_and_key(db_session, name="auto"): key = f"k-{uuid.uuid4().hex}" product = await create_product(db_session, name=name, api_key=key) return product, key def _payload_from_golden( case: str, *, tenant_ref: str = "t1", branch_ref: str = "b1", ver_proc: str = "sowai-auto/1b.1" ) -> tuple[dict, dict]: raw = json.loads((_GOLDENS_DIR / f"{case}.input.json").read_text(encoding="utf-8")) payload = { "tenant_ref": tenant_ref, "branch_ref": branch_ref, "document_model": "55", "serie": raw["serie"], "emitente": raw["emitente"], "itens": raw["itens"], "pagamento": raw["pagamento"], "ambiente": raw["ambiente"], "uf_destino_tipo": raw["uf_destino_tipo"], "destinatario": raw.get("destinatario"), "ver_proc": ver_proc, } return payload, raw async def _setup_certificate_and_series( db_session, client, key, *, tenant_ref, branch_ref, serie, next_number, cnpj=_CNPJ_EMITENTE, ): pfx_bytes = _build_test_pfx(cnpj=cnpj) upload_response = await client.post( "/v1/certificados", params={"tenant_ref": tenant_ref, "branch_ref": branch_ref}, files={"file": ("cert.pfx", pfx_bytes, "application/x-pkcs12")}, data={"password": "senha123", "cnpj": cnpj}, headers=_headers(key), ) assert upload_response.status_code == 201, upload_response.text series_response = await client.post( "/v1/series", json={ "tenant_ref": tenant_ref, "branch_ref": branch_ref, "document_model": "55", "serie": serie, "next_number": next_number, }, headers=_headers(key), ) assert series_response.status_code == 201, series_response.text return series_response.json() def _xsd_schema(): """Resolvido do pacote `nfelib` INSTALADO -- ausência é FALHA, não skip, mesma convenção do auto (`tests/modules/fiscal/test_emissao.py:: _xsd_schema`).""" import nfelib path = Path(nfelib.__file__).parent / "nfe" / "schemas" / "v4_0" / "nfe_v4.00.xsd" if not path.exists(): pytest.fail(f"XSD não encontrado em {path} -- validação XSD é prova OBRIGATÓRIA") return etree.XMLSchema(etree.parse(str(path))) @pytest.mark.asyncio async def test_emitir_documento_caminho_feliz_gera_documento_assinado_xsd_valido(db_session): product, key = await _product_and_key(db_session) payload, raw = _payload_from_golden("caso_padrao_intra") transport = ASGITransport(app=app) async with AsyncClient(transport=transport, base_url="http://test") as client: await _setup_certificate_and_series( db_session, client, key, tenant_ref="t1", branch_ref="b1", serie=raw["serie"], next_number=raw["numero"], ) response = await client.post( "/v1/emissoes", json=payload, headers={**_headers(key), "Idempotency-Key": f"idem-{uuid.uuid4().hex}"}, ) assert response.status_code == 201, response.text body = response.json() assert body["status"] == "ASSINADO" assert body["numero"] == raw["numero"] assert len(body["chave_acesso"]) == 44 and body["chave_acesso"].isdigit() assert body["tenant_ref"] == "t1" assert body["branch_ref"] == "b1" assert "xml_assinado" not in body result = await db_session.execute( select(FiscalDocument).where(FiscalDocument.id == uuid.UUID(body["id"])) ) document = result.scalar_one() assert "