"""Chave de acesso NF-e: cUF(2)+AAMM(4)+CNPJ(14)+mod(2)+série(3)+nNF(9)+ tpEmis(1)+cNF(8)+DV(1) = 44 dígitos. DV = módulo 11 (pesos 2..9, da direita para a esquerda; resto 0 ou 1 -> DV 0). `dv_modulo11` é validado contra chaves REAIS (não inventadas) -- mas NÃO as de `tests/modules/estoque/test_nfe_import.py` como o plano original cogitava: aquele fixture é hand-written (docstring do próprio arquivo o diz) e tem só UMA chave de 44 dígitos, cujo último dígito NÃO bate com o DV módulo 11 (conferido por script antes de escrever este teste -- não é uma chave real, é um ID de exemplo forjado só para exercitar o parser). Fonte usada em vez disso: os XMLs de amostra que o próprio pacote `nfelib` (dependência já pinada em pyproject.toml, versão instalada no pod confirmada em 2.5.2 na Task 1 Step 0) empacota em `nfelib/nfe/samples/v4_0/leiauteNFe/*.xml` -- os NOMES desses arquivos SÃO as chaves de acesso de NF-e reais/realistas de exemplo, com DV correto -- confirmado por decomposição manual antes de escrever este teste: `35180834128745000152550010000476711079516696` (mod=55, DV correto=6) e `35200159594315000157550010000000012062777161` (mod=55, DV correto=1, também o `Id` do `infNFe` dentro de `NFe35200159594315000157550010000000012062777161.xml`).""" import pytest from sowai_fiscal.chave_acesso import dv_modulo11, gerar_cnf, montar_chave_acesso def test_dv_modulo11_contra_chave_real_1_amostra_nfelib(): chave44 = "35180834128745000152550010000476711079516696" assert dv_modulo11(chave44[:43]) == chave44[43] def test_dv_modulo11_contra_chave_real_2_amostra_nfelib(): chave44 = "35200159594315000157550010000000012062777161" assert dv_modulo11(chave44[:43]) == chave44[43] def test_dv_modulo11_rejeita_chave_com_tamanho_errado(): with pytest.raises(ValueError): dv_modulo11("123") def test_montar_chave_tem_44_digitos_e_dv_valido(): chave = montar_chave_acesso( uf_ibge="41", aamm="2607", cnpj="12345678000190", modelo="55", serie=1, numero=1014, tp_emis="1", cnf="87654321", ) assert len(chave) == 44 and chave.isdigit() assert chave[-1] == dv_modulo11(chave[:43]) assert chave[22:25] == "001" and chave[25:34] == "000001014" def test_gerar_cnf_8_digitos_e_diferente_do_nnf(): for _ in range(50): cnf = gerar_cnf(numero_nnf=1014) assert len(cnf) == 8 and cnf.isdigit() assert int(cnf) != 1014 # regra NT2019.001: cNF != nNF def test_gerar_cnf_nunca_all_zeros(): # F3a (review 2026-07-16): all-zeros é 8 dígitos válidos no FORMATO mas # é o chute óbvio de um gerador fraco -- nunca deve sair do sorteio, # mesmo por acaso estatístico (1 em 10^8 por chamada; 200 chamadas aqui # deixa a chance de falso-negativo desprezível). for _ in range(200): assert gerar_cnf(numero_nnf=1014) != "00000000" def test_montar_chave_acesso_rejeita_componentes_que_se_compensam_no_total(): # F1 (review 2026-07-16, Important): o bug real -- checar SÓ o tamanho # agregado (43 dígitos) deixava passar um CNPJ de 13 dígitos (errado, # deveria ter 14) somado a um cNF de 9 dígitos (errado, deveria ter 8): # 13 + 9 = 22, o mesmo total de dígitos que o par correto 14 + 8 = 22 -- # a chave "compensada" saía com 43 dígitos e um DV módulo-11 sintaticamente # válido, mas apontando para o CNPJ ERRADO (um dígito do cNF vazando para # dentro do que deveria ser o CNPJ). A validação por COMPONENTE (não só o # total) tem que capturar isto. with pytest.raises(ValueError, match="cnpj"): montar_chave_acesso( uf_ibge="41", aamm="2607", cnpj="1234567800019", # 13 dígitos, não 14 modelo="55", serie=1, numero=1014, tp_emis="1", cnf="876543210", # 9 dígitos, não 8 -- compensa o total em 43 ) @pytest.mark.parametrize( "campo,valor", [ ("uf_ibge", "4"), ("uf_ibge", "411"), ("aamm", "260"), ("cnpj", "1234567800019"), ("cnpj", "123456780001900"), ("modelo", "5"), ("tp_emis", "12"), ("cnf", "8765432"), ("cnf", "876543210"), ], ) def test_montar_chave_acesso_rejeita_cada_componente_fora_do_tamanho(campo, valor): kwargs = dict( uf_ibge="41", aamm="2607", cnpj="12345678000190", modelo="55", serie=1, numero=1014, tp_emis="1", cnf="87654321", ) kwargs[campo] = valor with pytest.raises(ValueError): montar_chave_acesso(**kwargs) def test_montar_chave_acesso_rejeita_componente_nao_numerico(): with pytest.raises(ValueError, match="cnpj"): montar_chave_acesso( uf_ibge="41", aamm="2607", cnpj="1234567800019A", modelo="55", serie=1, numero=1014, tp_emis="1", cnf="87654321", )