use fiscal::types::*;
use rstest::rstest;
mod tools_test {
use super::*;
mod sefaz_consulta_recibo {
use super::*;
#[test]
#[should_panic]
fn throws_on_empty_recibo() {
let _ = fiscal::sefaz::request_builders::build_consulta_recibo_request(
"",
SefazEnvironment::Homologation,
);
}
#[test]
fn builds_correct_xml_for_valid_recibo() {
let xml = fiscal::sefaz::request_builders::build_consulta_recibo_request(
"143220020730398",
SefazEnvironment::Homologation,
);
assert!(xml.contains("<consReciNFe"));
assert!(xml.contains(r#"versao="4.00""#));
assert!(xml.contains("<tpAmb>2</tpAmb>"));
assert!(xml.contains("<nRec>143220020730398</nRec>"));
assert!(xml.contains("</consReciNFe>"));
}
}
mod sefaz_consulta_chave {
use super::*;
#[test]
#[should_panic]
fn throws_on_empty_chave() {
let _ = fiscal::sefaz::request_builders::build_consulta_request(
"",
SefazEnvironment::Homologation,
);
}
#[test]
fn builds_correct_xml_for_valid_chave() {
let chave = "43211105730928000145650010000002401717268120";
let xml = fiscal::sefaz::request_builders::build_consulta_request(
chave,
SefazEnvironment::Homologation,
);
assert!(xml.contains("<consSitNFe"));
assert!(xml.contains(r#"versao="4.00""#));
assert!(xml.contains("<tpAmb>2</tpAmb>"));
assert!(xml.contains("<xServ>CONSULTAR</xServ>"));
assert!(xml.contains(&format!("<chNFe>{chave}</chNFe>")));
assert!(xml.contains("</consSitNFe>"));
}
#[test]
#[should_panic]
fn throws_on_chave_length_not_44() {
let _ = fiscal::sefaz::request_builders::build_consulta_request(
"1234567890123456789012345678901234567890123",
SefazEnvironment::Homologation,
);
}
#[test]
#[should_panic]
fn throws_on_non_numeric_chave() {
let _ = fiscal::sefaz::request_builders::build_consulta_request(
"aqui temos uma chave nao numerica xxxxxxxxxx",
SefazEnvironment::Homologation,
);
}
}
mod sefaz_envia_lote {
#[test]
#[should_panic]
fn throws_on_invalid_parameter() {
let _ =
fiscal::sefaz::request_builders::build_autorizacao_request("", "1", false, false);
}
#[test]
fn builds_correct_request_for_model_65() {
let xml = r#"<NFe xmlns="http://www.portalfiscal.inf.br/nfe"><infNFe Id="NFe65"></infNFe></NFe>"#;
let id_lote = "1636667815";
let result = fiscal::sefaz::request_builders::build_autorizacao_request(
xml, id_lote, true, false,
);
assert!(result.contains("<enviNFe"));
assert!(result.contains(&format!("<idLote>{id_lote}</idLote>")));
assert!(result.contains("<indSinc>1</indSinc>"));
assert!(result.contains("</enviNFe>"));
assert!(!result.contains("<?xml"));
}
#[test]
fn builds_correct_request_for_model_55() {
let xml = r#"<NFe xmlns="http://www.portalfiscal.inf.br/nfe"><infNFe Id="NFe55"></infNFe></NFe>"#;
let id_lote = "1636667815";
let result = fiscal::sefaz::request_builders::build_autorizacao_request(
xml, id_lote, true, false,
);
assert!(result.contains("<enviNFe"));
assert!(result.contains(&format!("<idLote>{id_lote}</idLote>")));
assert!(result.contains("<indSinc>1</indSinc>"));
}
#[test]
fn builds_correct_compressed_request_for_model_55() {
let xml = r#"<NFe xmlns="http://www.portalfiscal.inf.br/nfe"><infNFe Id="NFe55"></infNFe></NFe>"#;
let id_lote = "1636667815";
let result = fiscal::sefaz::request_builders::build_autorizacao_request(
xml, id_lote, true, true,
);
assert!(result.contains("<enviNFe"));
assert!(result.contains(&format!("<idLote>{id_lote}</idLote>")));
}
}
mod sefaz_inutiliza {
use super::*;
#[test]
fn builds_correct_xml_for_number_voiding() {
let xml = fiscal::sefaz::request_builders::build_inutilizacao_request(
22,
"93623057000128",
"55",
1,
1,
10,
"Testando Inutilizacao",
SefazEnvironment::Homologation,
"SP",
);
assert!(xml.contains("<inutNFe"));
assert!(xml.contains(r#"versao="4.00""#));
assert!(xml.contains("<tpAmb>2</tpAmb>"));
assert!(xml.contains("<xServ>INUTILIZAR</xServ>"));
assert!(xml.contains("<cUF>35</cUF>"));
assert!(xml.contains("<ano>22</ano>"));
assert!(xml.contains("<CNPJ>93623057000128</CNPJ>"));
assert!(xml.contains("<mod>55</mod>"));
assert!(xml.contains("<serie>1</serie>"));
assert!(xml.contains("<nNFIni>1</nNFIni>"));
assert!(xml.contains("<nNFFin>10</nNFFin>"));
assert!(xml.contains("<xJust>Testando Inutilizacao</xJust>"));
assert!(xml.contains("</inutNFe>"));
assert!(xml.contains(r#"Id="ID"#));
}
}
mod sefaz_cadastro {
#[test]
fn builds_correct_cadastro_request_by_cnpj() {
let xml = fiscal::sefaz::request_builders::build_cadastro_request(
"RS",
"CNPJ",
"20532295000154",
);
assert!(xml.contains("<ConsCad"));
assert!(xml.contains(r#"versao="2.00""#));
assert!(xml.contains("<xServ>CONS-CAD</xServ>"));
assert!(xml.contains("<UF>RS</UF>"));
assert!(xml.contains("<CNPJ>20532295000154</CNPJ>"));
assert!(xml.contains("</ConsCad>"));
}
#[test]
fn builds_correct_cadastro_request_by_ie() {
let xml =
fiscal::sefaz::request_builders::build_cadastro_request("RS", "IE", "1234567");
assert!(xml.contains("<ConsCad"));
assert!(xml.contains("<UF>RS</UF>"));
assert!(xml.contains("<IE>1234567</IE>"));
assert!(!xml.contains("<CNPJ>"));
}
#[test]
fn builds_correct_cadastro_request_by_cpf() {
let xml =
fiscal::sefaz::request_builders::build_cadastro_request("RS", "CPF", "60140174028");
assert!(xml.contains("<ConsCad"));
assert!(xml.contains("<UF>RS</UF>"));
assert!(xml.contains("<CPF>60140174028</CPF>"));
assert!(!xml.contains("<CNPJ>"));
assert!(!xml.contains("<IE>"));
}
}
mod sefaz_status {
use super::*;
#[test]
fn builds_correct_status_request_xml() {
let xml = fiscal::sefaz::request_builders::build_status_request(
"RS",
SefazEnvironment::Homologation,
);
assert!(xml.contains("<consStatServ"));
assert!(xml.contains(r#"versao="4.00""#));
assert!(xml.contains("<tpAmb>2</tpAmb>"));
assert!(xml.contains("<cUF>43</cUF>"));
assert!(xml.contains("<xServ>STATUS</xServ>"));
assert!(xml.contains("</consStatServ>"));
}
}
mod sefaz_dist_dfe {
use super::*;
#[test]
fn builds_correct_dist_dfe_request_xml() {
let xml = fiscal::sefaz::request_builders::build_dist_dfe_request(
"SP",
"93623057000128",
None,
Some("000000000000200"),
SefazEnvironment::Homologation,
);
assert!(xml.contains("<distDFeInt"));
assert!(xml.contains(r#"versao="1.01""#));
assert!(xml.contains("<tpAmb>2</tpAmb>"));
assert!(xml.contains("<cUFAutor>35</cUFAutor>"));
assert!(xml.contains("<CNPJ>93623057000128</CNPJ>"));
assert!(xml.contains("<consNSU>"));
assert!(xml.contains("<NSU>000000000000200</NSU>"));
assert!(xml.contains("</distDFeInt>"));
}
}
mod sefaz_cce {
use super::*;
#[test]
fn builds_correct_cce_request_xml() {
let chave = "35220605730928000145550010000048661583302923";
let xml = fiscal::sefaz::request_builders::build_cce_request(
chave,
"Descricao da correcao",
1,
SefazEnvironment::Homologation,
"93623057000128",
);
assert!(xml.contains("<envEvento"));
assert!(xml.contains(&format!("ID110110{chave}01")));
assert!(xml.contains("<tpAmb>2</tpAmb>"));
assert!(xml.contains("<CNPJ>93623057000128</CNPJ>"));
assert!(xml.contains(&format!("<chNFe>{chave}</chNFe>")));
assert!(xml.contains("<tpEvento>110110</tpEvento>"));
assert!(xml.contains("<nSeqEvento>1</nSeqEvento>"));
assert!(xml.contains("<descEvento>Carta de Correcao</descEvento>"));
assert!(xml.contains("<xCorrecao>Descricao da correcao</xCorrecao>"));
assert!(xml.contains("<xCondUso>"));
assert!(xml.contains("</envEvento>"));
}
}
mod sefaz_cancela {
use super::*;
#[test]
fn builds_correct_cancellation_event_request_xml() {
let chave = "35150300822602000124550010009923461099234656";
let xml = fiscal::sefaz::request_builders::build_cancela_request(
chave,
"123456789101234",
"Preenchimento incorreto dos dados",
1,
SefazEnvironment::Homologation,
"93623057000128",
);
assert!(xml.contains("<envEvento"));
assert!(xml.contains(&format!("ID110111{chave}01")));
assert!(xml.contains("<tpEvento>110111</tpEvento>"));
assert!(xml.contains("<descEvento>Cancelamento</descEvento>"));
assert!(xml.contains("<nProt>123456789101234</nProt>"));
assert!(xml.contains("<xJust>Preenchimento incorreto dos dados</xJust>"));
}
}
mod sefaz_manifesta {
use super::*;
#[test]
fn builds_correct_manifestacao_request_xml() {
let chave = "35240305730928000145650010000001421071400478";
let xml = fiscal::sefaz::request_builders::build_manifesta_request(
chave,
"210210",
None,
1,
SefazEnvironment::Homologation,
"93623057000128",
);
assert!(xml.contains("<envEvento"));
assert!(xml.contains(&format!("ID210210{chave}01")));
assert!(xml.contains("<cOrgao>91</cOrgao>"));
assert!(xml.contains("<tpEvento>210210</tpEvento>"));
assert!(xml.contains("<descEvento>Ciencia da Operacao</descEvento>"));
assert!(!xml.contains("<xJust>"));
}
}
}
mod contingency_test {
use super::*;
#[test]
fn get_contingency_type_returns_valid_type() {
let ct = fiscal::contingency::contingency_for_state("SP");
let s = ct.as_str();
assert!(s == "svc-an" || s == "svc-rs");
}
#[test]
fn sp_defaults_to_svc_an() {
let ct = fiscal::contingency::contingency_for_state("SP");
assert_eq!(ct.as_str(), "svc-an");
}
#[test]
fn sp_should_not_use_svc_rs() {
let ct = fiscal::contingency::contingency_for_state("SP");
assert_ne!(ct.as_str(), "svc-rs");
}
#[test]
#[should_panic]
fn throws_on_motive_shorter_than_15_chars() {
let mut contingency = fiscal::contingency::Contingency::new();
contingency
.activate(ContingencyType::SvcAn, "Testes")
.unwrap();
}
#[test]
#[should_panic]
fn throws_on_motive_longer_than_255_chars() {
let mut contingency = fiscal::contingency::Contingency::new();
let motive = "Eu fui emitir uma NFe e a SEFAZ autorizadora estava fora do ar, \
entrei em contato com o tecnico de informatica que me mandou acionar o modo de contingencia, \
indicando o motivo. Nosso diretor esta exigindo a emissao da NFe agora, e sei nao sei mais o que fazer. \
Entao fiz essa tentativa agora.";
contingency
.activate(ContingencyType::SvcAn, motive)
.unwrap();
}
#[test]
fn am_defaults_to_svc_rs() {
let ct = fiscal::contingency::contingency_for_state("AM");
assert_eq!(ct.as_str(), "svc-rs");
}
#[test]
fn load_contingency_from_json() {
let json =
r#"{"motive":"Testes Unitarios","timestamp":1480700623,"type":"SVCAN","tpEmis":6}"#;
let contingency = fiscal::contingency::Contingency::load(json).expect("load failed");
assert!(contingency.contingency_type.is_some());
}
#[test]
fn deactivate_resets_contingency() {
let json =
r#"{"motive":"Testes Unitarios","timestamp":1480700623,"type":"SVCAN","tpEmis":6}"#;
let mut contingency = fiscal::contingency::Contingency::load(json).expect("load failed");
contingency.deactivate();
assert!(contingency.contingency_type.is_none());
}
#[rstest]
#[case("AC", "svc-an")]
#[case("AL", "svc-an")]
#[case("AP", "svc-an")]
#[case("CE", "svc-an")]
#[case("DF", "svc-an")]
#[case("ES", "svc-an")]
#[case("MG", "svc-an")]
#[case("PA", "svc-an")]
#[case("PB", "svc-an")]
#[case("PI", "svc-an")]
#[case("RJ", "svc-an")]
#[case("RN", "svc-an")]
#[case("RO", "svc-an")]
#[case("RR", "svc-an")]
#[case("RS", "svc-an")]
#[case("SC", "svc-an")]
#[case("SE", "svc-an")]
#[case("SP", "svc-an")]
#[case("TO", "svc-an")]
#[case("AM", "svc-rs")]
#[case("BA", "svc-rs")]
#[case("GO", "svc-rs")]
#[case("MA", "svc-rs")]
#[case("MS", "svc-rs")]
#[case("MT", "svc-rs")]
#[case("PE", "svc-rs")]
#[case("PR", "svc-rs")]
fn contingency_state_mapping(#[case] uf: &str, #[case] expected: &str) {
assert_eq!(
fiscal::contingency::contingency_for_state(uf).as_str(),
expected,
"State {uf} should map to {expected}"
);
}
#[test]
fn get_sefaz_url_contingency_sp_returns_svc_an_url() {
let url = fiscal::sefaz::urls::get_sefaz_url(
"SP",
SefazEnvironment::Homologation,
"NfeAutorizacao",
)
.expect("get_sefaz_url failed");
assert!(!url.is_empty());
}
}
mod contingency_nfe_test {
use super::*;
#[test]
fn adjusts_nfe_xml_with_contingency_data() {
let xml = std::fs::read_to_string(concat!(
env!("CARGO_MANIFEST_DIR"),
"/tests/fixtures/xml/nfe_layout4.xml"
))
.expect("Failed to read nfe_layout4.xml");
let mut cont = fiscal::contingency::Contingency::new();
cont.activate(
ContingencyType::SvcAn,
"Teste de uso da classe em contingencia",
)
.expect("activate failed");
let newxml =
fiscal::contingency::adjust_nfe_contingency(&xml, &cont).expect("adjust failed");
assert!(newxml.contains("<tpEmis>6</tpEmis>"));
assert!(newxml.contains("<dhCont>"));
assert!(newxml.contains("<xJust>Teste de uso da classe em contingencia</xJust>"));
assert!(!newxml.contains("<tpEmis>1</tpEmis>"));
}
#[test]
fn does_not_alter_xml_already_configured_for_contingency() {
let xml = std::fs::read_to_string(concat!(
env!("CARGO_MANIFEST_DIR"),
"/tests/fixtures/xml/nfe_layout4_contingencia_sem_assinatura.xml"
))
.expect("Failed to read contingency XML");
let mut cont = fiscal::contingency::Contingency::new();
cont.activate(ContingencyType::SvcAn, "Teste contingencia SVCAN")
.expect("activate failed");
let newxml =
fiscal::contingency::adjust_nfe_contingency(&xml, &cont).expect("adjust failed");
assert!(newxml.contains("<tpEmis>6</tpEmis>"));
assert!(newxml.contains("<dhCont>2024-06-11T23:30:41-03:00</dhCont>"));
assert!(newxml.contains("<xJust>Teste de uso da classe em conting"));
}
#[test]
fn throws_when_adjusting_nfce_xml_for_contingency() {
let xml = std::fs::read_to_string(concat!(
env!("CARGO_MANIFEST_DIR"),
"/tests/fixtures/xml/nfce.xml"
))
.expect("Failed to read nfce.xml");
let mut cont = fiscal::contingency::Contingency::new();
cont.activate(
ContingencyType::SvcAn,
"Teste de uso da classe em contingencia",
)
.expect("activate failed");
let result = fiscal::contingency::adjust_nfe_contingency(&xml, &cont);
assert!(result.is_err());
let err = result.unwrap_err().to_string();
assert!(
err.contains("65") || err.contains("NFC-e"),
"Error should mention model 65: {err}"
);
}
}
mod qrcode_test {
use super::*;
#[test]
fn inserts_qr_code_and_url_chave_tags_into_nfce_xml() {
let xml = std::fs::read_to_string(concat!(
env!("CARGO_MANIFEST_DIR"),
"/tests/fixtures/xml/nfce_sem_qrcode.xml"
))
.expect("Failed to read nfce_sem_qrcode.xml");
let result = fiscal::qrcode::put_qr_tag(
&PutQRTagParams::new(xml, "GPB0JBWLUR6HWFTVEAS6RJ69GPCROFPBBB8G", "000001", "200",
"https://www.homologacao.nfce.fazenda.sp.gov.br/NFCeConsultaPublica/Paginas/ConsultaQRCode.aspx", "")
).expect("put_qr_tag failed");
assert!(result.contains("<infNFeSupl>"));
assert!(result.contains("<qrCode>"));
assert!(result.contains("</qrCode>"));
assert!(result.contains("<urlChave>"));
assert!(result.contains("</infNFeSupl>"));
let inf_supl_pos = result.find("<infNFeSupl>").unwrap();
let sig_pos = result.find("<Signature").unwrap();
assert!(inf_supl_pos < sig_pos);
assert!(result.contains("29181033657677000156650010001654399001654399"));
}
#[test]
fn throws_when_csc_token_is_empty() {
let result = fiscal::qrcode::build_nfce_qr_code_url(
&NfceQrCodeParams::new("35200505730928000145650010000000121000000129", QrCodeVersion::V200, SefazEnvironment::Homologation, EmissionType::Normal,
"https://www.homologacao.nfce.fazenda.sp.gov.br/NFCeConsultaPublica/Paginas/ConsultaQRCode.aspx")
.csc_token("").csc_id("000001")
);
assert!(result.is_err());
let err = result.unwrap_err().to_string();
assert!(
err.contains("CSC token"),
"Error should mention CSC token: {err}"
);
}
#[test]
fn throws_when_csc_id_is_empty() {
let result = fiscal::qrcode::build_nfce_qr_code_url(
&NfceQrCodeParams::new("35200505730928000145650010000000121000000129", QrCodeVersion::V200, SefazEnvironment::Homologation, EmissionType::Normal,
"https://www.homologacao.nfce.fazenda.sp.gov.br/NFCeConsultaPublica/Paginas/ConsultaQRCode.aspx")
.csc_token("GPB0JBWLUR6HWFTVEAS6RJ69GPCROFPBBB8G").csc_id("")
);
assert!(result.is_err());
let err = result.unwrap_err().to_string();
assert!(err.contains("CSC ID"), "Error should mention CSC ID: {err}");
}
#[test]
fn produces_malformed_url_when_base_url_is_empty() {
let result = fiscal::qrcode::build_nfce_qr_code_url(
&NfceQrCodeParams::new(
"35200505730928000145650010000000121000000129",
QrCodeVersion::V200,
SefazEnvironment::Homologation,
EmissionType::Normal,
"",
)
.csc_token("GPB0JBWLUR6HWFTVEAS6RJ69GPCROFPBBB8G")
.csc_id("000001"),
);
if let Ok(url) = result {
assert!(url.starts_with("?p="))
}
}
}