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
BIN
View File
Binary file not shown.
@@ -0,0 +1,18 @@
<NFe xmlns="http://www.portalfiscal.inf.br/nfe"><infNFe versao="4.00" Id="NFe41260712345678000190550010000010051100000050"><ide><cUF>41</cUF><cNF>10000005</cNF><natOp>Venda</natOp><mod>55</mod><serie>1</serie><nNF>1005</nNF><dhEmi>2026-07-16T10:20:00-03:00</dhEmi><tpNF>1</tpNF><idDest>1</idDest><cMunFG>4106902</cMunFG><tpImp>1</tpImp><tpEmis>1</tpEmis><cDV>0</cDV><tpAmb>2</tpAmb><finNFe>1</finNFe><indFinal>1</indFinal><indPres>1</indPres><procEmi>0</procEmi><verProc>sowai-auto/1b.1</verProc></ide><emit><CNPJ>12345678000190</CNPJ><xNome>AUTOPECAS THIAGO LTDA</xNome><xFant>Thiago Auto Center</xFant><enderEmit><xLgr>Rua das Autopecas</xLgr><nro>1500</nro><xBairro>Centro</xBairro><cMun>4106902</cMun><xMun>Curitiba</xMun><UF>PR</UF><CEP>80000000</CEP><cPais>1058</cPais><xPais>Brasil</xPais><fone>4133334444</fone></enderEmit><IE>1234567890</IE><CRT>1</CRT></emit><dest><CNPJ>99887766000155</CNPJ><xNome>NF-E EMITIDA EM AMBIENTE DE HOMOLOGACAO - SEM VALOR FISCAL</xNome><enderDest><xLgr>Av Cliente</xLgr><nro>50</nro><xBairro>Bairro</xBairro><cMun>4106902</cMun><xMun>Curitiba</xMun><UF>PR</UF><CEP>80000100</CEP><cPais>1058</cPais><xPais>Brasil</xPais></enderDest><indIEDest>9</indIEDest></dest><det nItem="1"><prod><cProd>PC001</cProd><cEAN>SEM GTIN</cEAN><xProd>Filtro de oleo</xProd><NCM>84212300</NCM><CFOP>5102</CFOP><uCom>UN</uCom><qCom>1</qCom><vUnCom>100.00</vUnCom><vProd>100.00</vProd><cEANTrib>SEM GTIN</cEANTrib><uTrib>UN</uTrib><qTrib>1</qTrib><vUnTrib>100.00</vUnTrib><indTot>1</indTot></prod><imposto><ICMS><ICMSSN102><orig>0</orig><CSOSN>102</CSOSN></ICMSSN102></ICMS><PIS><PISNT><CST>08</CST></PISNT></PIS><COFINS><COFINSNT><CST>08</CST></COFINSNT></COFINS><IBSCBS><CST>000</CST><cClassTrib>000001</cClassTrib><gIBSCBS><vBC>100.00</vBC><gIBSUF><pIBSUF>0.1000</pIBSUF><vIBSUF>10.00</vIBSUF></gIBSUF><gCBS><pCBS>0.9000</pCBS><vCBS>90.00</vCBS></gCBS></gIBSCBS></IBSCBS></imposto></det><total><ICMSTot><vBC>0.00</vBC><vICMS>0.00</vICMS><vICMSDeson>0.00</vICMSDeson><vFCP>0.00</vFCP><vBCST>0.00</vBCST><vST>0.00</vST><vFCPST>0.00</vFCPST><vFCPSTRet>0.00</vFCPSTRet><vProd>100.00</vProd><vFrete>0.00</vFrete><vSeg>0.00</vSeg><vDesc>0.00</vDesc><vII>0.00</vII><vIPI>0.00</vIPI><vIPIDevol>0.00</vIPIDevol><vPIS>0.00</vPIS><vCOFINS>0.00</vCOFINS><vOutro>0.00</vOutro><vNF>100.00</vNF></ICMSTot></total><transp><modFrete>9</modFrete></transp><pag><detPag><indPag>0</indPag><tPag>01</tPag><vPag>100.00</vPag></detPag></pag></infNFe><Signature xmlns="http://www.w3.org/2000/09/xmldsig#"><SignedInfo><CanonicalizationMethod Algorithm="http://www.w3.org/TR/2001/REC-xml-c14n-20010315"/><SignatureMethod Algorithm="http://www.w3.org/2000/09/xmldsig#rsa-sha1"/><Reference URI="#NFe41260712345678000190550010000010051100000050"><Transforms><Transform Algorithm="http://www.w3.org/2000/09/xmldsig#enveloped-signature"/><Transform Algorithm="http://www.w3.org/TR/2001/REC-xml-c14n-20010315"/></Transforms><DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1"/><DigestValue>EIBfqHSPgzGamG7niiWJY8QjvaY=</DigestValue></Reference></SignedInfo><SignatureValue>lMywqdUdSFsbf5SR1kOmyByaFI1aSoUJwwgbz+P7Qri6iW+pJy+JJcapJnQcEdYbbFTqMJYJeRM87xHlQngg0SnPqd1ZjcLqZgXf6tm7GZriS3SNaMiXUC5EG9BnawvP4p7m5GviX3JJNKs2lvG0unBUtptq4aH/cZe5jd62SxeqyaeX4PxW/pbvkSBu3WndTLvM56vajMgimjbjQ8zLid85XJkVEAk9rV7KObZsHl0vuvFUqnZsAVaIJxB8nmmmcn/clT4AsPzhlkozR5YV0OjpP4sm08bYcvPcGcxxidxHE1o40xr6S6ozpiQENPEUBgTpNqMYaJTI0Rft3ERWaA==</SignatureValue><KeyInfo><X509Data><X509Certificate>MIIDHDCCAgSgAwIBAgIUOfWhuJYF+atF0Sv3tUJMbQzcRqwwDQYJKoZIhvcNAQEL
BQAwSDEtMCsGA1UEAwwkQVVUT1BFQ0FTIFRISUFHTyBMVERBOjEyMzQ1Njc4MDAw
MTkwMRcwFQYDVQQFEw4xMjM0NTY3ODAwMDE5MDAeFw0yNjA3MjExOTQ3NDNaFw0z
NjA3MTkxOTQ3NDNaMEgxLTArBgNVBAMMJEFVVE9QRUNBUyBUSElBR08gTFREQTox
MjM0NTY3ODAwMDE5MDEXMBUGA1UEBRMOMTIzNDU2NzgwMDAxOTAwggEiMA0GCSqG
SIb3DQEBAQUAA4IBDwAwggEKAoIBAQDZUIX5xprSUIivdhvDZSwLu8pug0QXNHGk
oIyyRjG9OIGd3nkn8X+9CP9yukYEyPvr1Yz/TM3pn/4d/kSixIi422kadKMka2QR
uax/dRwj0UZJwm7MnC/Osa3FgEln3Yz4S6HWEPwPa/PNeETiXTLtnIQiY1M0GYld
ccVsNUjDhvmjhIhuLzcZ3goWlKyLoeFoL8inFb+awFt6u2pao8JGyqAV3/7es5Ul
gpLg10KfKcJyvGPvDhFo723FKACdOse7clLtMOrRt0Z/ST9i+17txFo/OjwXWEhC
vGOTxNu667uA+1SzUk8ivouHOKtPX1ZdicEmTYY90Re/0B208nV7AgMBAAEwDQYJ
KoZIhvcNAQELBQADggEBAGAPlWqX6VSVviqyxQu4AstW0xfgd461wy0lTgfh4KQ4
7J6ikP5I+o8O9neSQ2bGaYbfTUja3Oq7VEoLtbumpZcmkrvlmXKmzUnkALoHlTab
ih+9p3In9HgUhgQ4wwrAFurQzrtmfsSKC9hqUZMSn1BYGEs3oa2ocE3JvkBZ7YF+
BLWs5cd/eqb4fPgDO6yZMnMOzTc9tjOhufW5eQKtGfTuR6YXqc7qRouFk+mqNbGA
wAW+WjFafx9FENC3HdEt7UhI2cOjVuC0TUIAKl3dH3HpebaI3O0Gbd/AA+sGYezz
lXy6GZUYxpBNNdP9avLSwabIMcoEN37ARvaY2VDULBQ=
</X509Certificate></X509Data></KeyInfo></Signature></NFe>
@@ -0,0 +1,18 @@
<NFe xmlns="http://www.portalfiscal.inf.br/nfe"><infNFe versao="4.00" Id="NFe41260712345678000190550010000010041100000045"><ide><cUF>41</cUF><cNF>10000004</cNF><natOp>Devolucao de venda</natOp><mod>55</mod><serie>1</serie><nNF>1004</nNF><dhEmi>2026-07-16T10:15:00-03:00</dhEmi><tpNF>1</tpNF><idDest>1</idDest><cMunFG>4106902</cMunFG><tpImp>1</tpImp><tpEmis>1</tpEmis><cDV>5</cDV><tpAmb>2</tpAmb><finNFe>1</finNFe><indFinal>1</indFinal><indPres>1</indPres><procEmi>0</procEmi><verProc>sowai-auto/1b.1</verProc></ide><emit><CNPJ>12345678000190</CNPJ><xNome>AUTOPECAS THIAGO LTDA</xNome><xFant>Thiago Auto Center</xFant><enderEmit><xLgr>Rua das Autopecas</xLgr><nro>1500</nro><xBairro>Centro</xBairro><cMun>4106902</cMun><xMun>Curitiba</xMun><UF>PR</UF><CEP>80000000</CEP><cPais>1058</cPais><xPais>Brasil</xPais><fone>4133334444</fone></enderEmit><IE>1234567890</IE><CRT>1</CRT></emit><dest><CNPJ>99887766000155</CNPJ><xNome>NF-E EMITIDA EM AMBIENTE DE HOMOLOGACAO - SEM VALOR FISCAL</xNome><enderDest><xLgr>Av Cliente</xLgr><nro>50</nro><xBairro>Bairro</xBairro><cMun>4106902</cMun><xMun>Curitiba</xMun><UF>PR</UF><CEP>80000100</CEP><cPais>1058</cPais><xPais>Brasil</xPais></enderDest><indIEDest>9</indIEDest></dest><det nItem="1"><prod><cProd>PC001</cProd><cEAN>SEM GTIN</cEAN><xProd>Filtro de oleo</xProd><NCM>84212300</NCM><CFOP>1202</CFOP><uCom>UN</uCom><qCom>1</qCom><vUnCom>75.00</vUnCom><vProd>75.00</vProd><cEANTrib>SEM GTIN</cEANTrib><uTrib>UN</uTrib><qTrib>1</qTrib><vUnTrib>75.00</vUnTrib><indTot>1</indTot></prod><imposto><ICMS><ICMSSN102><orig>0</orig><CSOSN>102</CSOSN></ICMSSN102></ICMS><PIS><PISNT><CST>08</CST></PISNT></PIS><COFINS><COFINSNT><CST>08</CST></COFINSNT></COFINS></imposto></det><total><ICMSTot><vBC>75.00</vBC><vICMS>0.00</vICMS><vICMSDeson>0.00</vICMSDeson><vFCP>0.00</vFCP><vBCST>0.00</vBCST><vST>0.00</vST><vFCPST>0.00</vFCPST><vFCPSTRet>0.00</vFCPSTRet><vProd>75.00</vProd><vFrete>0.00</vFrete><vSeg>0.00</vSeg><vDesc>0.00</vDesc><vII>0.00</vII><vIPI>0.00</vIPI><vIPIDevol>0.00</vIPIDevol><vPIS>0.00</vPIS><vCOFINS>0.00</vCOFINS><vOutro>0.00</vOutro><vNF>75.00</vNF></ICMSTot></total><transp><modFrete>9</modFrete></transp><pag><detPag><indPag>0</indPag><tPag>90</tPag><vPag>75.00</vPag></detPag></pag></infNFe><Signature xmlns="http://www.w3.org/2000/09/xmldsig#"><SignedInfo><CanonicalizationMethod Algorithm="http://www.w3.org/TR/2001/REC-xml-c14n-20010315"/><SignatureMethod Algorithm="http://www.w3.org/2000/09/xmldsig#rsa-sha1"/><Reference URI="#NFe41260712345678000190550010000010041100000045"><Transforms><Transform Algorithm="http://www.w3.org/2000/09/xmldsig#enveloped-signature"/><Transform Algorithm="http://www.w3.org/TR/2001/REC-xml-c14n-20010315"/></Transforms><DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1"/><DigestValue>dWH2Qpht8rN1pncCUL31+ymheWE=</DigestValue></Reference></SignedInfo><SignatureValue>iyBjOX1COZz2vFk1SNaQvmLqvsXGo63DINjVvAsoAA2g6XsnoLdrNjXOtxATh4Iez1SEcVoOex5V6x4/Vdd7EkX8TSu5QKqIAbDUjystojZAVW+wXj7enUgb24qbrsFU40EhEPUdZp51grKgm0d2R4qNeodXZiFGbuheRZQ4EhY5b+2EPX6kSdxGuYi4F0Xq0JgkG90CTJIUFNEDkY2PKCXOipNBpx6nOYiWnEBGfMw2oTmJeXQ2yKyXjrr01S9Lj48UZybGMIStQJoMTHn+p9+3ccTwbBAJ66Et38SEsP8gmWaLkGq5+zMCtriiwEJzCJMzTUMnDPee+/nada0KWg==</SignatureValue><KeyInfo><X509Data><X509Certificate>MIIDHDCCAgSgAwIBAgIUOfWhuJYF+atF0Sv3tUJMbQzcRqwwDQYJKoZIhvcNAQEL
BQAwSDEtMCsGA1UEAwwkQVVUT1BFQ0FTIFRISUFHTyBMVERBOjEyMzQ1Njc4MDAw
MTkwMRcwFQYDVQQFEw4xMjM0NTY3ODAwMDE5MDAeFw0yNjA3MjExOTQ3NDNaFw0z
NjA3MTkxOTQ3NDNaMEgxLTArBgNVBAMMJEFVVE9QRUNBUyBUSElBR08gTFREQTox
MjM0NTY3ODAwMDE5MDEXMBUGA1UEBRMOMTIzNDU2NzgwMDAxOTAwggEiMA0GCSqG
SIb3DQEBAQUAA4IBDwAwggEKAoIBAQDZUIX5xprSUIivdhvDZSwLu8pug0QXNHGk
oIyyRjG9OIGd3nkn8X+9CP9yukYEyPvr1Yz/TM3pn/4d/kSixIi422kadKMka2QR
uax/dRwj0UZJwm7MnC/Osa3FgEln3Yz4S6HWEPwPa/PNeETiXTLtnIQiY1M0GYld
ccVsNUjDhvmjhIhuLzcZ3goWlKyLoeFoL8inFb+awFt6u2pao8JGyqAV3/7es5Ul
gpLg10KfKcJyvGPvDhFo723FKACdOse7clLtMOrRt0Z/ST9i+17txFo/OjwXWEhC
vGOTxNu667uA+1SzUk8ivouHOKtPX1ZdicEmTYY90Re/0B208nV7AgMBAAEwDQYJ
KoZIhvcNAQELBQADggEBAGAPlWqX6VSVviqyxQu4AstW0xfgd461wy0lTgfh4KQ4
7J6ikP5I+o8O9neSQ2bGaYbfTUja3Oq7VEoLtbumpZcmkrvlmXKmzUnkALoHlTab
ih+9p3In9HgUhgQ4wwrAFurQzrtmfsSKC9hqUZMSn1BYGEs3oa2ocE3JvkBZ7YF+
BLWs5cd/eqb4fPgDO6yZMnMOzTc9tjOhufW5eQKtGfTuR6YXqc7qRouFk+mqNbGA
wAW+WjFafx9FENC3HdEt7UhI2cOjVuC0TUIAKl3dH3HpebaI3O0Gbd/AA+sGYezz
lXy6GZUYxpBNNdP9avLSwabIMcoEN37ARvaY2VDULBQ=
</X509Certificate></X509Data></KeyInfo></Signature></NFe>
@@ -0,0 +1,18 @@
<NFe xmlns="http://www.portalfiscal.inf.br/nfe"><infNFe versao="4.00" Id="NFe41260712345678000190550010000010061100000066"><ide><cUF>41</cUF><cNF>10000006</cNF><natOp>Venda</natOp><mod>55</mod><serie>1</serie><nNF>1006</nNF><dhEmi>2026-07-16T10:25:00-03:00</dhEmi><tpNF>1</tpNF><idDest>1</idDest><cMunFG>4106902</cMunFG><tpImp>1</tpImp><tpEmis>1</tpEmis><cDV>6</cDV><tpAmb>2</tpAmb><finNFe>1</finNFe><indFinal>1</indFinal><indPres>1</indPres><procEmi>0</procEmi><verProc>sowai-auto/1b.1</verProc></ide><emit><CNPJ>12345678000190</CNPJ><xNome>AUTOPECAS THIAGO LTDA</xNome><xFant>Thiago Auto Center</xFant><enderEmit><xLgr>Rua das Autopecas</xLgr><nro>1500</nro><xBairro>Centro</xBairro><cMun>4106902</cMun><xMun>Curitiba</xMun><UF>PR</UF><CEP>80000000</CEP><cPais>1058</cPais><xPais>Brasil</xPais><fone>4133334444</fone></enderEmit><IE>1234567890</IE><CRT>1</CRT></emit><dest><CNPJ>99887766000155</CNPJ><xNome>NF-E EMITIDA EM AMBIENTE DE HOMOLOGACAO - SEM VALOR FISCAL</xNome><enderDest><xLgr>Av Cliente</xLgr><nro>50</nro><xBairro>Bairro</xBairro><cMun>4106902</cMun><xMun>Curitiba</xMun><UF>PR</UF><CEP>80000100</CEP><cPais>1058</cPais><xPais>Brasil</xPais></enderDest><indIEDest>9</indIEDest></dest><det nItem="1"><prod><cProd>PC900</cProd><cEAN>SEM GTIN</cEAN><xProd>Arruela avulsa</xProd><NCM>73181900</NCM><CFOP>5102</CFOP><uCom>UN</uCom><qCom>1.5</qCom><vUnCom>0.01</vUnCom><vProd>0.02</vProd><cEANTrib>SEM GTIN</cEANTrib><uTrib>UN</uTrib><qTrib>1.5</qTrib><vUnTrib>0.01</vUnTrib><indTot>1</indTot></prod><imposto><ICMS><ICMSSN102><orig>0</orig><CSOSN>102</CSOSN></ICMSSN102></ICMS><PIS><PISNT><CST>08</CST></PISNT></PIS><COFINS><COFINSNT><CST>08</CST></COFINSNT></COFINS></imposto></det><det nItem="2"><prod><cProd>PC901</cProd><cEAN>SEM GTIN</cEAN><xProd>Arruela avulsa 2</xProd><NCM>73181900</NCM><CFOP>5102</CFOP><uCom>UN</uCom><qCom>1.5</qCom><vUnCom>0.01</vUnCom><vProd>0.02</vProd><cEANTrib>SEM GTIN</cEANTrib><uTrib>UN</uTrib><qTrib>1.5</qTrib><vUnTrib>0.01</vUnTrib><indTot>1</indTot></prod><imposto><ICMS><ICMSSN102><orig>0</orig><CSOSN>102</CSOSN></ICMSSN102></ICMS><PIS><PISNT><CST>08</CST></PISNT></PIS><COFINS><COFINSNT><CST>08</CST></COFINSNT></COFINS></imposto></det><total><ICMSTot><vBC>0.04</vBC><vICMS>0.00</vICMS><vICMSDeson>0.00</vICMSDeson><vFCP>0.00</vFCP><vBCST>0.00</vBCST><vST>0.00</vST><vFCPST>0.00</vFCPST><vFCPSTRet>0.00</vFCPSTRet><vProd>0.04</vProd><vFrete>0.00</vFrete><vSeg>0.00</vSeg><vDesc>0.00</vDesc><vII>0.00</vII><vIPI>0.00</vIPI><vIPIDevol>0.00</vIPIDevol><vPIS>0.00</vPIS><vCOFINS>0.00</vCOFINS><vOutro>0.00</vOutro><vNF>0.04</vNF></ICMSTot></total><transp><modFrete>9</modFrete></transp><pag><detPag><indPag>0</indPag><tPag>01</tPag><vPag>0.04</vPag></detPag></pag></infNFe><Signature xmlns="http://www.w3.org/2000/09/xmldsig#"><SignedInfo><CanonicalizationMethod Algorithm="http://www.w3.org/TR/2001/REC-xml-c14n-20010315"/><SignatureMethod Algorithm="http://www.w3.org/2000/09/xmldsig#rsa-sha1"/><Reference URI="#NFe41260712345678000190550010000010061100000066"><Transforms><Transform Algorithm="http://www.w3.org/2000/09/xmldsig#enveloped-signature"/><Transform Algorithm="http://www.w3.org/TR/2001/REC-xml-c14n-20010315"/></Transforms><DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1"/><DigestValue>UrxPctSfvF8tf9cYULFIdWbEUMA=</DigestValue></Reference></SignedInfo><SignatureValue>Bdi9HkxrMIPD7dtfd8wq1Mg2hCt+ZFffU3n79WqAxcDDLgtjCOb/vX4hvXj+J+LZ+SEcNzKFLF5kRyriHb7+e9YkS7BFgljNOGZunqkTz6/ZdmrPNlZB89z6SOB5aI+Z+J2KHtOO4l7kyIji5EdJl+NrDfRoCpBFFctBVMKCKtwBh31KoNDWq0XzqmfI+FDfZskdgR+W0AGFg6pGbNfv4dhpOAPT5/UY8HQDU4Zv6hVleUv/ifsqKTKzEa7z9/IL+5894NoPk9JyhNAFgKQDs4y2r1irykOsu1LHH+BvnPPCGKeKCLsVxqTgQxseKkfhiis2hTt97e365a7UmZ1JaA==</SignatureValue><KeyInfo><X509Data><X509Certificate>MIIDHDCCAgSgAwIBAgIUOfWhuJYF+atF0Sv3tUJMbQzcRqwwDQYJKoZIhvcNAQEL
BQAwSDEtMCsGA1UEAwwkQVVUT1BFQ0FTIFRISUFHTyBMVERBOjEyMzQ1Njc4MDAw
MTkwMRcwFQYDVQQFEw4xMjM0NTY3ODAwMDE5MDAeFw0yNjA3MjExOTQ3NDNaFw0z
NjA3MTkxOTQ3NDNaMEgxLTArBgNVBAMMJEFVVE9QRUNBUyBUSElBR08gTFREQTox
MjM0NTY3ODAwMDE5MDEXMBUGA1UEBRMOMTIzNDU2NzgwMDAxOTAwggEiMA0GCSqG
SIb3DQEBAQUAA4IBDwAwggEKAoIBAQDZUIX5xprSUIivdhvDZSwLu8pug0QXNHGk
oIyyRjG9OIGd3nkn8X+9CP9yukYEyPvr1Yz/TM3pn/4d/kSixIi422kadKMka2QR
uax/dRwj0UZJwm7MnC/Osa3FgEln3Yz4S6HWEPwPa/PNeETiXTLtnIQiY1M0GYld
ccVsNUjDhvmjhIhuLzcZ3goWlKyLoeFoL8inFb+awFt6u2pao8JGyqAV3/7es5Ul
gpLg10KfKcJyvGPvDhFo723FKACdOse7clLtMOrRt0Z/ST9i+17txFo/OjwXWEhC
vGOTxNu667uA+1SzUk8ivouHOKtPX1ZdicEmTYY90Re/0B208nV7AgMBAAEwDQYJ
KoZIhvcNAQELBQADggEBAGAPlWqX6VSVviqyxQu4AstW0xfgd461wy0lTgfh4KQ4
7J6ikP5I+o8O9neSQ2bGaYbfTUja3Oq7VEoLtbumpZcmkrvlmXKmzUnkALoHlTab
ih+9p3In9HgUhgQ4wwrAFurQzrtmfsSKC9hqUZMSn1BYGEs3oa2ocE3JvkBZ7YF+
BLWs5cd/eqb4fPgDO6yZMnMOzTc9tjOhufW5eQKtGfTuR6YXqc7qRouFk+mqNbGA
wAW+WjFafx9FENC3HdEt7UhI2cOjVuC0TUIAKl3dH3HpebaI3O0Gbd/AA+sGYezz
lXy6GZUYxpBNNdP9avLSwabIMcoEN37ARvaY2VDULBQ=
</X509Certificate></X509Data></KeyInfo></Signature></NFe>
@@ -0,0 +1,18 @@
<NFe xmlns="http://www.portalfiscal.inf.br/nfe"><infNFe versao="4.00" Id="NFe41260712345678000190550010000010021100000024"><ide><cUF>41</cUF><cNF>10000002</cNF><natOp>Venda</natOp><mod>55</mod><serie>1</serie><nNF>1002</nNF><dhEmi>2026-07-16T10:05:00-03:00</dhEmi><tpNF>1</tpNF><idDest>2</idDest><cMunFG>4106902</cMunFG><tpImp>1</tpImp><tpEmis>1</tpEmis><cDV>4</cDV><tpAmb>2</tpAmb><finNFe>1</finNFe><indFinal>1</indFinal><indPres>1</indPres><procEmi>0</procEmi><verProc>sowai-auto/1b.1</verProc></ide><emit><CNPJ>12345678000190</CNPJ><xNome>AUTOPECAS THIAGO LTDA</xNome><xFant>Thiago Auto Center</xFant><enderEmit><xLgr>Rua das Autopecas</xLgr><nro>1500</nro><xBairro>Centro</xBairro><cMun>4106902</cMun><xMun>Curitiba</xMun><UF>PR</UF><CEP>80000000</CEP><cPais>1058</cPais><xPais>Brasil</xPais><fone>4133334444</fone></enderEmit><IE>1234567890</IE><CRT>1</CRT></emit><dest><CNPJ>11222333000144</CNPJ><xNome>NF-E EMITIDA EM AMBIENTE DE HOMOLOGACAO - SEM VALOR FISCAL</xNome><enderDest><xLgr>Av Paulista</xLgr><nro>1000</nro><xCpl>Sala 10</xCpl><xBairro>Bela Vista</xBairro><cMun>3550308</cMun><xMun>Sao Paulo</xMun><UF>SP</UF><CEP>01310000</CEP><cPais>1058</cPais><xPais>Brasil</xPais></enderDest><indIEDest>1</indIEDest><IE>1122334455</IE><email>compras@clienteinter.com.br</email></dest><det nItem="1"><prod><cProd>PC001</cProd><cEAN>SEM GTIN</cEAN><xProd>Filtro de oleo</xProd><NCM>84212300</NCM><CFOP>6102</CFOP><uCom>UN</uCom><qCom>10</qCom><vUnCom>75.00</vUnCom><vProd>750.00</vProd><cEANTrib>SEM GTIN</cEANTrib><uTrib>UN</uTrib><qTrib>10</qTrib><vUnTrib>75.00</vUnTrib><indTot>1</indTot></prod><imposto><ICMS><ICMSSN102><orig>0</orig><CSOSN>102</CSOSN></ICMSSN102></ICMS><PIS><PISNT><CST>08</CST></PISNT></PIS><COFINS><COFINSNT><CST>08</CST></COFINSNT></COFINS></imposto></det><total><ICMSTot><vBC>750.00</vBC><vICMS>0.00</vICMS><vICMSDeson>0.00</vICMSDeson><vFCP>0.00</vFCP><vBCST>0.00</vBCST><vST>0.00</vST><vFCPST>0.00</vFCPST><vFCPSTRet>0.00</vFCPSTRet><vProd>750.00</vProd><vFrete>0.00</vFrete><vSeg>0.00</vSeg><vDesc>0.00</vDesc><vII>0.00</vII><vIPI>0.00</vIPI><vIPIDevol>0.00</vIPIDevol><vPIS>0.00</vPIS><vCOFINS>0.00</vCOFINS><vOutro>0.00</vOutro><vNF>750.00</vNF></ICMSTot></total><transp><modFrete>9</modFrete></transp><pag><detPag><indPag>0</indPag><tPag>17</tPag><vPag>750.00</vPag></detPag></pag></infNFe><Signature xmlns="http://www.w3.org/2000/09/xmldsig#"><SignedInfo><CanonicalizationMethod Algorithm="http://www.w3.org/TR/2001/REC-xml-c14n-20010315"/><SignatureMethod Algorithm="http://www.w3.org/2000/09/xmldsig#rsa-sha1"/><Reference URI="#NFe41260712345678000190550010000010021100000024"><Transforms><Transform Algorithm="http://www.w3.org/2000/09/xmldsig#enveloped-signature"/><Transform Algorithm="http://www.w3.org/TR/2001/REC-xml-c14n-20010315"/></Transforms><DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1"/><DigestValue>wMN3wf/wyPYVP/p8oSMeLIwQ0sI=</DigestValue></Reference></SignedInfo><SignatureValue>YTzAGw2xRWQSgMsJuF1XB2elWaTAHRRnLvOwM3Bo6YHNGVM3AqViOlhf0jRmbuQe4VCj8ugSEAe24p50Mn0YGLqH91emub4ADsewNAjeIcb7C9n/cYk8Sh2e/wPc3DvS5cOSICUOroPak0A3bWFb8DjRAcUdYvX/uI0IrzMlN0Qxzjd+lZwo/1cmzIWnW0G+nDvd2rykGOZTwCxd+yZf+qHw+A1iLFgeI9YHyWcWoUtKH746GvlJQ59Oz4dTXgtJL3Nnhx2O7dsgbU0/ee8Q6bYGELZz6Jtdup3EOVI7T5K/BBgVeNpchQfj7hHdL1gzmVAnGdVnBR01EDQnscVtZw==</SignatureValue><KeyInfo><X509Data><X509Certificate>MIIDHDCCAgSgAwIBAgIUOfWhuJYF+atF0Sv3tUJMbQzcRqwwDQYJKoZIhvcNAQEL
BQAwSDEtMCsGA1UEAwwkQVVUT1BFQ0FTIFRISUFHTyBMVERBOjEyMzQ1Njc4MDAw
MTkwMRcwFQYDVQQFEw4xMjM0NTY3ODAwMDE5MDAeFw0yNjA3MjExOTQ3NDNaFw0z
NjA3MTkxOTQ3NDNaMEgxLTArBgNVBAMMJEFVVE9QRUNBUyBUSElBR08gTFREQTox
MjM0NTY3ODAwMDE5MDEXMBUGA1UEBRMOMTIzNDU2NzgwMDAxOTAwggEiMA0GCSqG
SIb3DQEBAQUAA4IBDwAwggEKAoIBAQDZUIX5xprSUIivdhvDZSwLu8pug0QXNHGk
oIyyRjG9OIGd3nkn8X+9CP9yukYEyPvr1Yz/TM3pn/4d/kSixIi422kadKMka2QR
uax/dRwj0UZJwm7MnC/Osa3FgEln3Yz4S6HWEPwPa/PNeETiXTLtnIQiY1M0GYld
ccVsNUjDhvmjhIhuLzcZ3goWlKyLoeFoL8inFb+awFt6u2pao8JGyqAV3/7es5Ul
gpLg10KfKcJyvGPvDhFo723FKACdOse7clLtMOrRt0Z/ST9i+17txFo/OjwXWEhC
vGOTxNu667uA+1SzUk8ivouHOKtPX1ZdicEmTYY90Re/0B208nV7AgMBAAEwDQYJ
KoZIhvcNAQELBQADggEBAGAPlWqX6VSVviqyxQu4AstW0xfgd461wy0lTgfh4KQ4
7J6ikP5I+o8O9neSQ2bGaYbfTUja3Oq7VEoLtbumpZcmkrvlmXKmzUnkALoHlTab
ih+9p3In9HgUhgQ4wwrAFurQzrtmfsSKC9hqUZMSn1BYGEs3oa2ocE3JvkBZ7YF+
BLWs5cd/eqb4fPgDO6yZMnMOzTc9tjOhufW5eQKtGfTuR6YXqc7qRouFk+mqNbGA
wAW+WjFafx9FENC3HdEt7UhI2cOjVuC0TUIAKl3dH3HpebaI3O0Gbd/AA+sGYezz
lXy6GZUYxpBNNdP9avLSwabIMcoEN37ARvaY2VDULBQ=
</X509Certificate></X509Data></KeyInfo></Signature></NFe>
@@ -0,0 +1,18 @@
<NFe xmlns="http://www.portalfiscal.inf.br/nfe"><infNFe versao="4.00" Id="NFe41260712345678000190550010000010011100000019"><ide><cUF>41</cUF><cNF>10000001</cNF><natOp>Venda</natOp><mod>55</mod><serie>1</serie><nNF>1001</nNF><dhEmi>2026-07-16T10:00:00-03:00</dhEmi><tpNF>1</tpNF><idDest>1</idDest><cMunFG>4106902</cMunFG><tpImp>1</tpImp><tpEmis>1</tpEmis><cDV>9</cDV><tpAmb>2</tpAmb><finNFe>1</finNFe><indFinal>1</indFinal><indPres>1</indPres><procEmi>0</procEmi><verProc>sowai-auto/1b.1</verProc></ide><emit><CNPJ>12345678000190</CNPJ><xNome>AUTOPECAS THIAGO LTDA</xNome><xFant>Thiago Auto Center</xFant><enderEmit><xLgr>Rua das Autopecas</xLgr><nro>1500</nro><xBairro>Centro</xBairro><cMun>4106902</cMun><xMun>Curitiba</xMun><UF>PR</UF><CEP>80000000</CEP><cPais>1058</cPais><xPais>Brasil</xPais><fone>4133334444</fone></enderEmit><IE>1234567890</IE><CRT>1</CRT></emit><dest><CNPJ>99887766000155</CNPJ><xNome>NF-E EMITIDA EM AMBIENTE DE HOMOLOGACAO - SEM VALOR FISCAL</xNome><enderDest><xLgr>Av Cliente</xLgr><nro>50</nro><xBairro>Bairro</xBairro><cMun>4106902</cMun><xMun>Curitiba</xMun><UF>PR</UF><CEP>80000100</CEP><cPais>1058</cPais><xPais>Brasil</xPais></enderDest><indIEDest>9</indIEDest></dest><det nItem="1"><prod><cProd>PC001</cProd><cEAN>SEM GTIN</cEAN><xProd>Filtro de oleo</xProd><NCM>84212300</NCM><CFOP>5102</CFOP><uCom>UN</uCom><qCom>2</qCom><vUnCom>75.00</vUnCom><vProd>150.00</vProd><cEANTrib>SEM GTIN</cEANTrib><uTrib>UN</uTrib><qTrib>2</qTrib><vUnTrib>75.00</vUnTrib><indTot>1</indTot></prod><imposto><ICMS><ICMSSN102><orig>0</orig><CSOSN>102</CSOSN></ICMSSN102></ICMS><PIS><PISNT><CST>08</CST></PISNT></PIS><COFINS><COFINSNT><CST>08</CST></COFINSNT></COFINS></imposto></det><det nItem="2"><prod><cProd>PC002</cProd><cEAN>SEM GTIN</cEAN><xProd>Pastilha de freio</xProd><NCM>87083090</NCM><CFOP>5102</CFOP><uCom>PC</uCom><qCom>4</qCom><vUnCom>45.50</vUnCom><vProd>182.00</vProd><cEANTrib>SEM GTIN</cEANTrib><uTrib>PC</uTrib><qTrib>4</qTrib><vUnTrib>45.50</vUnTrib><indTot>1</indTot></prod><imposto><ICMS><ICMSSN102><orig>0</orig><CSOSN>102</CSOSN></ICMSSN102></ICMS><PIS><PISNT><CST>08</CST></PISNT></PIS><COFINS><COFINSNT><CST>08</CST></COFINSNT></COFINS></imposto></det><total><ICMSTot><vBC>332.00</vBC><vICMS>0.00</vICMS><vICMSDeson>0.00</vICMSDeson><vFCP>0.00</vFCP><vBCST>0.00</vBCST><vST>0.00</vST><vFCPST>0.00</vFCPST><vFCPSTRet>0.00</vFCPSTRet><vProd>332.00</vProd><vFrete>0.00</vFrete><vSeg>0.00</vSeg><vDesc>0.00</vDesc><vII>0.00</vII><vIPI>0.00</vIPI><vIPIDevol>0.00</vIPIDevol><vPIS>0.00</vPIS><vCOFINS>0.00</vCOFINS><vOutro>0.00</vOutro><vNF>332.00</vNF></ICMSTot></total><transp><modFrete>9</modFrete></transp><pag><detPag><indPag>0</indPag><tPag>01</tPag><vPag>332.00</vPag></detPag></pag></infNFe><Signature xmlns="http://www.w3.org/2000/09/xmldsig#"><SignedInfo><CanonicalizationMethod Algorithm="http://www.w3.org/TR/2001/REC-xml-c14n-20010315"/><SignatureMethod Algorithm="http://www.w3.org/2000/09/xmldsig#rsa-sha1"/><Reference URI="#NFe41260712345678000190550010000010011100000019"><Transforms><Transform Algorithm="http://www.w3.org/2000/09/xmldsig#enveloped-signature"/><Transform Algorithm="http://www.w3.org/TR/2001/REC-xml-c14n-20010315"/></Transforms><DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1"/><DigestValue>1inHNlmRb0maeftKaq/JExhNc8A=</DigestValue></Reference></SignedInfo><SignatureValue>ked+Fn1wxCy5EdNT289eLof7cqwuWvj1bh/AV0CQ3zbrLD5kL5/fXj2pLMZWS9wyl3NG8ZFATJ5B1xmeDcc0Niigs9O+e9wwWgX+3STDHZOogLuOU/CKhgFq/6e4wkZlMhV7Yffo+swo5qf82itGxUkkQkD00TDuFP7OnKOZBW8Ti+CPPPm+HLv613+BolP14If/2BXJW9HZiSu4Cua66/E32RSqPSp319DPvvpGwefJWDddOYfK9TkChirgdygAQUrkeJutcQdzzo9LnSVKE3FAu6q5w3rkNIrqA0OTEctGpojwfXc9E+IzRehof6fPvDCUjwSe89fP4JpK6tYiYA==</SignatureValue><KeyInfo><X509Data><X509Certificate>MIIDHDCCAgSgAwIBAgIUOfWhuJYF+atF0Sv3tUJMbQzcRqwwDQYJKoZIhvcNAQEL
BQAwSDEtMCsGA1UEAwwkQVVUT1BFQ0FTIFRISUFHTyBMVERBOjEyMzQ1Njc4MDAw
MTkwMRcwFQYDVQQFEw4xMjM0NTY3ODAwMDE5MDAeFw0yNjA3MjExOTQ3NDNaFw0z
NjA3MTkxOTQ3NDNaMEgxLTArBgNVBAMMJEFVVE9QRUNBUyBUSElBR08gTFREQTox
MjM0NTY3ODAwMDE5MDEXMBUGA1UEBRMOMTIzNDU2NzgwMDAxOTAwggEiMA0GCSqG
SIb3DQEBAQUAA4IBDwAwggEKAoIBAQDZUIX5xprSUIivdhvDZSwLu8pug0QXNHGk
oIyyRjG9OIGd3nkn8X+9CP9yukYEyPvr1Yz/TM3pn/4d/kSixIi422kadKMka2QR
uax/dRwj0UZJwm7MnC/Osa3FgEln3Yz4S6HWEPwPa/PNeETiXTLtnIQiY1M0GYld
ccVsNUjDhvmjhIhuLzcZ3goWlKyLoeFoL8inFb+awFt6u2pao8JGyqAV3/7es5Ul
gpLg10KfKcJyvGPvDhFo723FKACdOse7clLtMOrRt0Z/ST9i+17txFo/OjwXWEhC
vGOTxNu667uA+1SzUk8ivouHOKtPX1ZdicEmTYY90Re/0B208nV7AgMBAAEwDQYJ
KoZIhvcNAQELBQADggEBAGAPlWqX6VSVviqyxQu4AstW0xfgd461wy0lTgfh4KQ4
7J6ikP5I+o8O9neSQ2bGaYbfTUja3Oq7VEoLtbumpZcmkrvlmXKmzUnkALoHlTab
ih+9p3In9HgUhgQ4wwrAFurQzrtmfsSKC9hqUZMSn1BYGEs3oa2ocE3JvkBZ7YF+
BLWs5cd/eqb4fPgDO6yZMnMOzTc9tjOhufW5eQKtGfTuR6YXqc7qRouFk+mqNbGA
wAW+WjFafx9FENC3HdEt7UhI2cOjVuC0TUIAKl3dH3HpebaI3O0Gbd/AA+sGYezz
lXy6GZUYxpBNNdP9avLSwabIMcoEN37ARvaY2VDULBQ=
</X509Certificate></X509Data></KeyInfo></Signature></NFe>
@@ -0,0 +1,18 @@
<NFe xmlns="http://www.portalfiscal.inf.br/nfe"><infNFe versao="4.00" Id="NFe41260712345678000190550010000010031100000030"><ide><cUF>41</cUF><cNF>10000003</cNF><natOp>Venda</natOp><mod>55</mod><serie>1</serie><nNF>1003</nNF><dhEmi>2026-07-16T10:10:00-03:00</dhEmi><tpNF>1</tpNF><idDest>1</idDest><cMunFG>4106902</cMunFG><tpImp>1</tpImp><tpEmis>1</tpEmis><cDV>0</cDV><tpAmb>2</tpAmb><finNFe>1</finNFe><indFinal>1</indFinal><indPres>1</indPres><procEmi>0</procEmi><verProc>sowai-auto/1b.1</verProc></ide><emit><CNPJ>12345678000190</CNPJ><xNome>AUTOPECAS THIAGO LTDA</xNome><xFant>Thiago Auto Center</xFant><enderEmit><xLgr>Rua das Autopecas</xLgr><nro>1500</nro><xBairro>Centro</xBairro><cMun>4106902</cMun><xMun>Curitiba</xMun><UF>PR</UF><CEP>80000000</CEP><cPais>1058</cPais><xPais>Brasil</xPais><fone>4133334444</fone></enderEmit><IE>1234567890</IE><CRT>1</CRT></emit><dest><CPF>12345678909</CPF><xNome>NF-E EMITIDA EM AMBIENTE DE HOMOLOGACAO - SEM VALOR FISCAL</xNome><enderDest><xLgr>Rua Oficina</xLgr><nro>22</nro><xBairro>Industrial</xBairro><cMun>4106902</cMun><xMun>Curitiba</xMun><UF>PR</UF><CEP>80000200</CEP><cPais>1058</cPais><xPais>Brasil</xPais></enderDest><indIEDest>9</indIEDest></dest><det nItem="1"><prod><cProd>OL500</cProd><cEAN>SEM GTIN</cEAN><xProd>Oleo lubrificante 15W40 1L</xProd><NCM>27101259</NCM><CEST>0600100</CEST><CFOP>5405</CFOP><uCom>UN</uCom><qCom>1</qCom><vUnCom>100.00</vUnCom><vProd>100.00</vProd><cEANTrib>SEM GTIN</cEANTrib><uTrib>UN</uTrib><qTrib>1</qTrib><vUnTrib>100.00</vUnTrib><indTot>1</indTot></prod><imposto><ICMS><ICMSSN500><orig>0</orig><CSOSN>500</CSOSN><vBCSTRet>140.00</vBCSTRet><pST>18.0000</pST><vICMSSTRet>13.20</vICMSSTRet></ICMSSN500></ICMS><PIS><PISNT><CST>04</CST></PISNT></PIS><COFINS><COFINSNT><CST>04</CST></COFINSNT></COFINS></imposto></det><total><ICMSTot><vBC>0.00</vBC><vICMS>0.00</vICMS><vICMSDeson>0.00</vICMSDeson><vFCP>0.00</vFCP><vBCST>140.00</vBCST><vST>13.20</vST><vFCPST>0.00</vFCPST><vFCPSTRet>0.00</vFCPSTRet><vProd>100.00</vProd><vFrete>0.00</vFrete><vSeg>0.00</vSeg><vDesc>0.00</vDesc><vII>0.00</vII><vIPI>0.00</vIPI><vIPIDevol>0.00</vIPIDevol><vPIS>0.00</vPIS><vCOFINS>0.00</vCOFINS><vOutro>0.00</vOutro><vNF>100.00</vNF></ICMSTot></total><transp><modFrete>9</modFrete></transp><pag><detPag><indPag>0</indPag><tPag>01</tPag><vPag>100.00</vPag></detPag></pag></infNFe><Signature xmlns="http://www.w3.org/2000/09/xmldsig#"><SignedInfo><CanonicalizationMethod Algorithm="http://www.w3.org/TR/2001/REC-xml-c14n-20010315"/><SignatureMethod Algorithm="http://www.w3.org/2000/09/xmldsig#rsa-sha1"/><Reference URI="#NFe41260712345678000190550010000010031100000030"><Transforms><Transform Algorithm="http://www.w3.org/2000/09/xmldsig#enveloped-signature"/><Transform Algorithm="http://www.w3.org/TR/2001/REC-xml-c14n-20010315"/></Transforms><DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1"/><DigestValue>GynTwfOg2ofaLE8WyWWwdpLBjWg=</DigestValue></Reference></SignedInfo><SignatureValue>1KMXWaK4qME2/kgazsMvEIB0B2Oxr6bLCNtfa6IrWd/i+muv9CDRhOm/yKumdRdmC6wys0o0krkwOW0Zh91iCikvHVyO/sh4ze4ycFzE2cODFpEciS7+59pMZM4vARhoZga855QKTzCB6CvFx99VAeq7X0wxXc7FWEmz2ENH6Vn0pkFkiMlpIgT1yFtGZbOQ5lcuE0hGTQJlEool/dbZRWWNp2CPbpx4Hys/CwLUM/MIA7lgfOqvEE0VxrfsU4Cfk2azBylFmprGnySot6HMPl0nkmGoVWEnaX3hxIlJJjfHXPZMnRp/HeryZpNb5GRzWjW9oJlXZUkzPcc02gkI8g==</SignatureValue><KeyInfo><X509Data><X509Certificate>MIIDHDCCAgSgAwIBAgIUOfWhuJYF+atF0Sv3tUJMbQzcRqwwDQYJKoZIhvcNAQEL
BQAwSDEtMCsGA1UEAwwkQVVUT1BFQ0FTIFRISUFHTyBMVERBOjEyMzQ1Njc4MDAw
MTkwMRcwFQYDVQQFEw4xMjM0NTY3ODAwMDE5MDAeFw0yNjA3MjExOTQ3NDNaFw0z
NjA3MTkxOTQ3NDNaMEgxLTArBgNVBAMMJEFVVE9QRUNBUyBUSElBR08gTFREQTox
MjM0NTY3ODAwMDE5MDEXMBUGA1UEBRMOMTIzNDU2NzgwMDAxOTAwggEiMA0GCSqG
SIb3DQEBAQUAA4IBDwAwggEKAoIBAQDZUIX5xprSUIivdhvDZSwLu8pug0QXNHGk
oIyyRjG9OIGd3nkn8X+9CP9yukYEyPvr1Yz/TM3pn/4d/kSixIi422kadKMka2QR
uax/dRwj0UZJwm7MnC/Osa3FgEln3Yz4S6HWEPwPa/PNeETiXTLtnIQiY1M0GYld
ccVsNUjDhvmjhIhuLzcZ3goWlKyLoeFoL8inFb+awFt6u2pao8JGyqAV3/7es5Ul
gpLg10KfKcJyvGPvDhFo723FKACdOse7clLtMOrRt0Z/ST9i+17txFo/OjwXWEhC
vGOTxNu667uA+1SzUk8ivouHOKtPX1ZdicEmTYY90Re/0B208nV7AgMBAAEwDQYJ
KoZIhvcNAQELBQADggEBAGAPlWqX6VSVviqyxQu4AstW0xfgd461wy0lTgfh4KQ4
7J6ikP5I+o8O9neSQ2bGaYbfTUja3Oq7VEoLtbumpZcmkrvlmXKmzUnkALoHlTab
ih+9p3In9HgUhgQ4wwrAFurQzrtmfsSKC9hqUZMSn1BYGEs3oa2ocE3JvkBZ7YF+
BLWs5cd/eqb4fPgDO6yZMnMOzTc9tjOhufW5eQKtGfTuR6YXqc7qRouFk+mqNbGA
wAW+WjFafx9FENC3HdEt7UhI2cOjVuC0TUIAKl3dH3HpebaI3O0Gbd/AA+sGYezz
lXy6GZUYxpBNNdP9avLSwabIMcoEN37ARvaY2VDULBQ=
</X509Certificate></X509Data></KeyInfo></Signature></NFe>
+400
View File
@@ -0,0 +1,400 @@
"""Task 6: suíte de CONTRATO HTTP -- design spec's "os 3 seguros de
portabilidade", item (a): "suíte de contrato HTTP language-agnostic (bate na
API, não no interior)". A REGRA DESTE ARQUIVO: zero import de `fiscal_svc.*`
além de `fiscal_svc.main.app` (para montar o `ASGITransport` -- inevitável
em Python; um cliente HTTP real bateria numa URL, não precisaria nem disso).
Nenhum import de model/service/schema interno -- é a suíte que uma
implementação GO deste MESMO contrato (`docs/openapi-v1.json`) teria que
passar batendo na porta 8140 de fora, sem NUNCA ver este código-fonte.
Provisionamento de fixtures (criar um Product/API key) é feito via
`scripts/create_product.py`'s MESMA função (`tenancy.service.create_
product`) em qualquer OUTRO arquivo de teste deste repo -- mas aqui, para
manter a regra "zero import do interior", cada teste usa o endpoint HTTP
disponível (upload de certificado, criação de série) e trata a criação do
Product como um pré-requisito de infraestrutura resolvido pela fixture
`product_api_key` (que É a única concessão: sem UM jeito de provisionar um
Product por HTTP -- design spec decisão #5, "provisionar um Product é uma
ação de operador, não self-service" -- não há como popular um cenário sem
tocar a camada de app UMA vez por teste; a asserção em si nunca o faz)."""
import uuid
from datetime import datetime, timedelta, timezone
import pytest
import pytest_asyncio
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 fiscal_svc.core.db import get_session
from fiscal_svc.main import app
_CNPJ = "14200166000187"
@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"))
yield
@pytest_asyncio.fixture
async def product_api_key(db_session):
"""A ÚNICA concessão à regra "zero import do interior" -- provisionar
um Product não tem endpoint HTTP (decisão de design deliberada, ver o
docstring do módulo), então esta fixture chama a função de serviço
diretamente. Nenhum teste abaixo importa nada além disto."""
from fiscal_svc.tenancy.service import create_product
key = f"k-{uuid.uuid4().hex}"
await create_product(db_session, name=f"contract-{uuid.uuid4().hex[:8]}", api_key=key)
return key
def _headers(api_key: str) -> dict[str, str]:
return {"X-Api-Key": api_key}
def _build_test_pfx(cnpj: str = _CNPJ, 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"EMPRESA TESTE 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")),
)
# --- health ------------------------------------------------------------
@pytest.mark.asyncio
async def test_health_returns_200_ok_shape():
transport = ASGITransport(app=app)
async with AsyncClient(transport=transport, base_url="http://test") as client:
response = await client.get("/v1/health")
assert response.status_code == 200
assert response.json() == {"status": "ok"}
# --- auth (require_product) --------------------------------------------
@pytest.mark.asyncio
@pytest.mark.parametrize(
"method,path,kwargs",
[
("get", "/v1/certificados", {"params": {"tenant_ref": "t", "branch_ref": "b"}}),
("get", "/v1/series", {"params": {"tenant_ref": "t", "branch_ref": "b"}}),
("get", "/v1/documentos", {}),
("post", "/v1/series", {"json": {"tenant_ref": "t", "branch_ref": "b", "document_model": "55", "serie": 1, "next_number": 1}}),
],
)
async def test_every_v1_route_requires_api_key(method, path, kwargs):
transport = ASGITransport(app=app)
async with AsyncClient(transport=transport, base_url="http://test") as client:
response = await getattr(client, method)(path, **kwargs)
assert response.status_code == 401, response.text
@pytest.mark.asyncio
async def test_wrong_api_key_is_401():
transport = ASGITransport(app=app)
async with AsyncClient(transport=transport, base_url="http://test") as client:
response = await client.get(
"/v1/series",
params={"tenant_ref": "t", "branch_ref": "b"},
headers=_headers("not-a-real-key"),
)
assert response.status_code == 401, response.text
# --- series --------------------------------------------------------------
@pytest.mark.asyncio
async def test_series_create_list_patch_shapes(product_api_key):
transport = ASGITransport(app=app)
async with AsyncClient(transport=transport, base_url="http://test") as client:
create_response = await client.post(
"/v1/series",
json={"tenant_ref": "t1", "branch_ref": "b1", "document_model": "55", "serie": 1, "next_number": 1},
headers=_headers(product_api_key),
)
assert create_response.status_code == 201, create_response.text
body = create_response.json()
for field in ("id", "product_id", "tenant_ref", "branch_ref", "document_model", "serie", "next_number"):
assert field in body, f"campo {field!r} ausente no shape de FiscalSeriesRead"
series_id = body["id"]
duplicate_response = await client.post(
"/v1/series",
json={"tenant_ref": "t1", "branch_ref": "b1", "document_model": "55", "serie": 1, "next_number": 2},
headers=_headers(product_api_key),
)
assert duplicate_response.status_code == 409, duplicate_response.text
assert duplicate_response.json()["detail"]["code"] == "duplicate_fiscal_series"
list_response = await client.get(
"/v1/series", params={"tenant_ref": "t1", "branch_ref": "b1"}, headers=_headers(product_api_key)
)
assert list_response.status_code == 200, list_response.text
assert isinstance(list_response.json(), list)
assert len(list_response.json()) == 1
patch_response = await client.patch(
f"/v1/series/{series_id}", json={"next_number": 5}, headers=_headers(product_api_key)
)
assert patch_response.status_code == 200, patch_response.text
assert patch_response.json()["next_number"] == 5
not_found_response = await client.patch(
f"/v1/series/{uuid.uuid4()}", json={"next_number": 5}, headers=_headers(product_api_key)
)
assert not_found_response.status_code == 404, not_found_response.text
# --- certificados ----------------------------------------------------------
@pytest.mark.asyncio
async def test_certificados_upload_get_delete_shapes(product_api_key):
transport = ASGITransport(app=app)
async with AsyncClient(transport=transport, base_url="http://test") as client:
upload_response = await client.post(
"/v1/certificados",
params={"tenant_ref": "t1", "branch_ref": "b1"},
files={"file": ("cert.pfx", _build_test_pfx(), "application/x-pkcs12")},
data={"password": "senha123", "cnpj": _CNPJ},
headers=_headers(product_api_key),
)
assert upload_response.status_code == 201, upload_response.text
body = upload_response.json()
for field in (
"id", "product_id", "tenant_ref", "branch_ref", "cnpj",
"subject_cn", "cnpj_certificado", "not_valid_before", "not_valid_after", "created_at",
):
assert field in body, f"campo {field!r} ausente no shape de FiscalCertificateRead"
assert "pfx_encrypted" not in body
assert "password_encrypted" not in body
get_response = await client.get(
"/v1/certificados", params={"tenant_ref": "t1", "branch_ref": "b1"}, headers=_headers(product_api_key)
)
assert get_response.status_code == 200, get_response.text
delete_response = await client.delete(
"/v1/certificados", params={"tenant_ref": "t1", "branch_ref": "b1"}, headers=_headers(product_api_key)
)
assert delete_response.status_code == 204, delete_response.text
get_after_delete_response = await client.get(
"/v1/certificados", params={"tenant_ref": "t1", "branch_ref": "b1"}, headers=_headers(product_api_key)
)
assert get_after_delete_response.status_code == 404, get_after_delete_response.text
@pytest.mark.asyncio
async def test_certificado_invalido_e_422(product_api_key):
transport = ASGITransport(app=app)
async with AsyncClient(transport=transport, base_url="http://test") as client:
response = await client.post(
"/v1/certificados",
params={"tenant_ref": "t1", "branch_ref": "b1"},
files={"file": ("cert.pfx", b"nao e um pfx", "application/x-pkcs12")},
data={"password": "qualquer", "cnpj": _CNPJ},
headers=_headers(product_api_key),
)
assert response.status_code == 422, response.text
# --- emissão ---------------------------------------------------------------
def _minimal_emissao_payload(*, tenant_ref="t1", branch_ref="b1", serie=1) -> dict:
return {
"tenant_ref": tenant_ref,
"branch_ref": branch_ref,
"document_model": "55",
"serie": serie,
"emitente": {
"cnpj": _CNPJ,
"razao_social": "EMPRESA TESTE LTDA",
"nome_fantasia": None,
"ie": "1234567890",
"crt": "1",
"address_street": "Rua Teste",
"address_number": "100",
"address_complement": None,
"address_district": "Centro",
"address_city": "Curitiba",
"address_state": "PR",
"address_zip": "80000000",
"address_city_ibge_code": "4106902",
},
"itens": [
{
"codigo": "PC001",
"descricao": "Peca teste",
"ncm": "84212300",
"cfop": "5102",
"unidade_comercial": "UN",
"unidade_tributavel": "UN",
"quantidade": "1",
"valor_unitario": "10.00",
"fiscal_result": {
"cfop": "5102",
"cst": None,
"csosn": "102",
"origem": "0",
"consumidor_final": True,
"indicador_ie": "9",
"tributos": [],
},
}
],
"pagamento": {"tpag": "01", "valor": "10.00", "indpag": "0"},
"ambiente": "homologacao",
"uf_destino_tipo": "interna",
"destinatario": None,
"ver_proc": "contract-test/1.0",
}
@pytest.mark.asyncio
async def test_emissao_requires_idempotency_key_header(product_api_key):
transport = ASGITransport(app=app)
async with AsyncClient(transport=transport, base_url="http://test") as client:
response = await client.post(
"/v1/emissoes", json=_minimal_emissao_payload(), headers=_headers(product_api_key)
)
assert response.status_code == 422, response.text
@pytest.mark.asyncio
async def test_emissao_requires_ver_proc(product_api_key):
payload = _minimal_emissao_payload()
del payload["ver_proc"]
transport = ASGITransport(app=app)
async with AsyncClient(transport=transport, base_url="http://test") as client:
response = await client.post(
"/v1/emissoes", json=payload,
headers={**_headers(product_api_key), "Idempotency-Key": f"k-{uuid.uuid4().hex}"},
)
assert response.status_code == 422, response.text
@pytest.mark.asyncio
async def test_emissao_without_certificate_or_series_is_409_fiscal_config_missing(product_api_key):
transport = ASGITransport(app=app)
async with AsyncClient(transport=transport, base_url="http://test") as client:
response = await client.post(
"/v1/emissoes", json=_minimal_emissao_payload(),
headers={**_headers(product_api_key), "Idempotency-Key": f"k-{uuid.uuid4().hex}"},
)
assert response.status_code == 409, response.text
body = response.json()
assert body["detail"]["code"] == "fiscal_config_missing"
assert "message" in body["detail"]
@pytest.mark.asyncio
async def test_full_emissao_happy_path_shapes_and_idempotency(product_api_key):
transport = ASGITransport(app=app)
async with AsyncClient(transport=transport, base_url="http://test") as client:
upload_response = await client.post(
"/v1/certificados",
params={"tenant_ref": "t1", "branch_ref": "b1"},
files={"file": ("cert.pfx", _build_test_pfx(), "application/x-pkcs12")},
data={"password": "senha123", "cnpj": _CNPJ},
headers=_headers(product_api_key),
)
assert upload_response.status_code == 201, upload_response.text
series_response = await client.post(
"/v1/series",
json={"tenant_ref": "t1", "branch_ref": "b1", "document_model": "55", "serie": 1, "next_number": 1},
headers=_headers(product_api_key),
)
assert series_response.status_code == 201, series_response.text
idem_key = f"k-{uuid.uuid4().hex}"
first_response = await client.post(
"/v1/emissoes", json=_minimal_emissao_payload(),
headers={**_headers(product_api_key), "Idempotency-Key": idem_key},
)
assert first_response.status_code == 201, first_response.text
body = first_response.json()
for field in (
"id", "product_id", "tenant_ref", "branch_ref", "series_id", "document_model",
"serie", "numero", "chave_acesso", "codigo_numerico", "status", "ambiente",
"rejeicao_codigo", "rejeicao_motivo", "protocolo", "autorizada_em", "created_at",
):
assert field in body, f"campo {field!r} ausente no shape de FiscalDocumentRead"
assert "xml_assinado" not in body
assert body["status"] == "ASSINADO"
document_id = body["id"]
second_response = await client.post(
"/v1/emissoes", json=_minimal_emissao_payload(),
headers={**_headers(product_api_key), "Idempotency-Key": idem_key},
)
assert second_response.status_code == 200, second_response.text
assert second_response.json()["id"] == document_id
get_response = await client.get(f"/v1/documentos/{document_id}", headers=_headers(product_api_key))
assert get_response.status_code == 200, get_response.text
assert "xml_assinado" not in get_response.json()
xml_response = await client.get(f"/v1/documentos/{document_id}/xml", headers=_headers(product_api_key))
assert xml_response.status_code == 200
assert xml_response.headers["content-type"].startswith("application/xml")
assert "<NFe" in xml_response.text
list_response = await client.get(
"/v1/documentos", params={"tenant_ref": "t1", "branch_ref": "b1"}, headers=_headers(product_api_key)
)
assert list_response.status_code == 200, list_response.text
assert any(d["id"] == document_id for d in list_response.json())
not_found_response = await client.get(
f"/v1/documentos/{uuid.uuid4()}", headers=_headers(product_api_key)
)
assert not_found_response.status_code == 404, not_found_response.text
+31
View File
@@ -0,0 +1,31 @@
"""Task 6: design spec's "os 3 seguros de portabilidade", item (c) --
"OpenAPI versionado como fonte de verdade do contrato" -- and the F2 plan's
own requirement: `docs/openapi-v1.json` COMMITTED + a test that fails if it
diverges from `app.openapi()`. Regenerate with:
uv run python -c "
import json
from fiscal_svc.main import app
with open('docs/openapi-v1.json', 'w') as f:
json.dump(app.openapi(), f, indent=2, sort_keys=True)
f.write('\n')
"
...and commit the result -- NEVER hand-edit `docs/openapi-v1.json`."""
import json
from pathlib import Path
from fiscal_svc.main import app
_OPENAPI_PATH = Path(__file__).resolve().parents[1] / "docs" / "openapi-v1.json"
def test_committed_openapi_matches_generated_schema():
committed = json.loads(_OPENAPI_PATH.read_text(encoding="utf-8"))
generated = app.openapi()
assert committed == generated, (
"docs/openapi-v1.json divergiu do schema gerado por app.openapi() -- "
"regenere (ver o docstring deste teste) e commite o resultado; nunca "
"edite o JSON à mão"
)
+160
View File
@@ -0,0 +1,160 @@
"""Task 6: fecha o gap da EMENDA 7b (design spec's "os 3 seguros de
portabilidade", item (b)) -- o corpus golden da lib (`sowai_fiscal.
goldens`) só cobre XML PRÉ-assinatura; o caminho de ASSINATURA fica sem
seguro golden até aqui. Para cada caso golden, este teste emite ATRAVÉS DA
API real (`POST /v1/emissoes`, o mesmo caminho de qualquer produto
consumidor) com `cnf`/`dh_emi` PINADOS (via monkeypatch -- os únicos dois
elementos do payload real que a SERVICE gera de forma não-determinística;
`numero` é controlado plantando o `next_number` da série) e o certificado de
TESTE fixo (`tests/fixtures/test_cert.pfx`, NUNCA um certificado real) e
compara o XML assinado resultante, BYTE A BYTE, contra `tests/
goldens_signed/<case>.expected.xml` (gerado uma vez por
`scripts/generate_signed_goldens.py`).
RSA PKCS#1v1.5 (o que `erpbrasil.assinatura` usa) não tem padding
aleatório -- a MESMA chave privada assinando o MESMO XML canonicalizado
produz sempre a MESMA assinatura. Isso é o que torna esta comparação byte a
byte estável entre execuções (provado por `scripts/generate_signed_goldens.
py` sendo rodado duas vezes seguidas e comparado -- ver seu commit)."""
import importlib.resources
import json
import uuid
from datetime import datetime
from pathlib import Path
import pytest
from httpx import ASGITransport, AsyncClient
from fiscal_svc.core.db import get_session
from fiscal_svc.emission import service as emission_service
from fiscal_svc.main import app
from fiscal_svc.tenancy.service import create_product
_GOLDENS_DIR = Path(str(importlib.resources.files("sowai_fiscal") / "goldens"))
_EXPECTED_DIR = Path(__file__).resolve().parent / "goldens_signed"
_CERT_PATH = Path(__file__).resolve().parent / "fixtures" / "test_cert.pfx"
_CERT_PASSWORD = "test-cert-password"
_CASES = sorted(p.name.removesuffix(".input.json") for p in _GOLDENS_DIR.glob("*.input.json"))
@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):
from cryptography.fernet import Fernet
from fiscal_svc.certificates import crypto as certificate_lib
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 _headers(api_key: str) -> dict[str, str]:
return {"X-Api-Key": api_key}
class _FrozenDatetime:
"""Stand-in for `emission.service`'s module-level `datetime` NAME
(`from datetime import datetime` -- a plain attribute of the module,
monkeypatch-replaceable). Exposes only `.now(tz)`, the ONLY method
`emission.service` calls on it -- returns the SAME fixed instant
regardless of the requested `tz` (converted via `.astimezone`), so both
call sites (`datetime.now(_TZ_EMISSAO)` for `dh_emi`/AAMM and `datetime.
now(timezone.utc)` for the certificate validity check) see a coherent,
pinned "now"."""
def __init__(self, fixed: datetime):
self._fixed = fixed
def now(self, tz=None):
return self._fixed.astimezone(tz) if tz is not None else self._fixed
@pytest.mark.parametrize("case", _CASES)
@pytest.mark.asyncio
async def test_signed_golden_matches_byte_for_byte(db_session, monkeypatch, case):
raw = json.loads((_GOLDENS_DIR / f"{case}.input.json").read_text(encoding="utf-8"))
expected = (_EXPECTED_DIR / f"{case}.expected.xml").read_text(encoding="utf-8")
# cNF pinado -- o único elemento aleatório de `emitir_documento` além do
# relógio.
monkeypatch.setattr(emission_service, "gerar_cnf", lambda numero: raw["cnf"])
# dh_emi/AAMM pinados no MESMO instante do golden.
fixed_now = datetime.fromisoformat(raw["dh_emi"])
monkeypatch.setattr(emission_service, "datetime", _FrozenDatetime(fixed_now))
key = f"k-{uuid.uuid4().hex}"
product = await create_product(db_session, name=f"golden-{case}", api_key=key)
transport = ASGITransport(app=app)
async with AsyncClient(transport=transport, base_url="http://test") as client:
pfx_bytes = _CERT_PATH.read_bytes()
upload_response = await client.post(
"/v1/certificados",
params={"tenant_ref": "t1", "branch_ref": "b1"},
files={"file": ("cert.pfx", pfx_bytes, "application/x-pkcs12")},
data={"password": _CERT_PASSWORD, "cnpj": raw["emitente"]["cnpj"]},
headers=_headers(key),
)
assert upload_response.status_code == 201, upload_response.text
# `numero` é controlado plantando o `next_number` da série -- a
# primeira (e única) alocação desta série devolve exatamente
# `raw["numero"]`.
series_response = await client.post(
"/v1/series",
json={
"tenant_ref": "t1", "branch_ref": "b1", "document_model": "55",
"serie": raw["serie"], "next_number": raw["numero"],
},
headers=_headers(key),
)
assert series_response.status_code == 201, series_response.text
payload = {
"tenant_ref": "t1",
"branch_ref": "b1",
"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"),
# A lib congela `ver_proc="sowai-auto/1b.1"` como default do
# PRÓPRIO `DadosEmissao` quando o golden JSON não o especifica
# (todos os 6 casos hoje) -- reproduzido aqui EXPLICITAMENTE
# (design spec EMENDA F1: este serviço nunca aplica esse default
# sozinho, o produto chamador sempre declara).
"ver_proc": raw.get("ver_proc", "sowai-auto/1b.1"),
}
emit_response = await client.post(
"/v1/emissoes", json=payload,
headers={**_headers(key), "Idempotency-Key": f"golden-{case}-{uuid.uuid4().hex}"},
)
assert emit_response.status_code == 201, emit_response.text
document_id = emit_response.json()["id"]
xml_response = await client.get(f"/v1/documentos/{document_id}/xml", headers=_headers(key))
assert emit_response.json()["chave_acesso"] == raw["chave_acesso"], (
"a chave de acesso montada pelo serviço deveria bater com a do golden -- todos os "
"componentes (uf_ibge/aamm/cnpj/modelo/serie/numero/tp_emis/cnf) precisam coincidir"
)
assert xml_response.text == expected, (
f"XML assinado do caso {case!r} divergiu byte a byte do golden esperado -- "
"regenere com scripts/generate_signed_goldens.py SE E SOMENTE SE a mudança for "
"intencional (ex.: golden novo/atualizado na lib), nunca para 'fazer passar'"
)