mod common;
use fiscal::newtypes::{Cents, IbgeCode, Rate};
use fiscal::types::*;
use fiscal::xml_builder::InvoiceBuilder;
use fiscal::xml_utils::{TagContent, tag};
use common::expect_xml_tag_values as expect_xml_contains;
fn minimal_nfe55_item() -> InvoiceItemData {
InvoiceItemData::new(
1,
"001",
"Produto Teste",
"61091000",
"5102",
"UN",
10.0,
Cents(1000),
Cents(10000),
"00",
Rate(1800),
Cents(1800),
"07",
"07",
)
.icms_mod_bc(0)
}
fn minimal_nfe55(number: u32) -> InvoiceBuilder {
use chrono::FixedOffset;
let offset = FixedOffset::west_opt(3 * 3600).unwrap();
let dt = chrono::NaiveDate::from_ymd_opt(2017, 3, 3)
.unwrap()
.and_hms_opt(11, 30, 0)
.unwrap()
.and_local_timezone(offset)
.unwrap();
InvoiceBuilder::new(
IssuerData::new(
"58716523000119",
"123456789012",
"EMPRESA TESTE LTDA",
TaxRegime::Normal,
"SP",
IbgeCode("3550308".to_string()),
"Sao Paulo",
"Rua Teste",
"100",
"Centro",
"01001000",
)
.trade_name("EMPRESA TESTE"),
SefazEnvironment::Homologation,
InvoiceModel::Nfe,
)
.series(1)
.invoice_number(number)
.issued_at(dt)
.recipient(RecipientData::new("11222333000181", "CLIENTE TESTE").state_code("SP"))
.items(vec![minimal_nfe55_item()])
.payments(vec![PaymentData::new("01", Cents(10000))])
}
mod deep_coverage_test {
mod make_render_coverage {
use super::super::*;
#[test]
fn monta_nfe_is_alias_for_render() {
let built = minimal_nfe55(30).build().expect("build failed");
assert!(!built.xml().is_empty());
assert!(built.xml().contains("<NFe"));
}
#[test]
fn set_only_ascii_converts_accented_characters() {
let mut item = minimal_nfe55_item();
item.description = "Açúcar Cristal".to_string();
item.quantity = 1.0;
item.total_price = Cents(1000);
item.icms_amount = Cents(180);
let built = minimal_nfe55(30)
.operation_nature("OPERAÇÃO DE VENDA")
.only_ascii(true)
.items(vec![item])
.payments(vec![PaymentData::new("01", Cents(1000))])
.build()
.expect("build should succeed");
let xml = built.xml();
assert!(
xml.contains("Acucar Cristal"),
"Expected 'Acucar Cristal' in XML, got:\n{xml}"
);
assert!(
xml.contains("OPERACAO DE VENDA"),
"Expected 'OPERACAO DE VENDA' in XML, got:\n{xml}"
);
assert!(
!xml.contains("Açúcar"),
"XML should not contain accented 'Açúcar'"
);
assert!(
!xml.contains("OPERAÇÃO"),
"XML should not contain accented 'OPERAÇÃO'"
);
}
#[test]
fn set_only_ascii_disabled_preserves_accents() {
let mut item = minimal_nfe55_item();
item.description = "Açúcar Cristal".to_string();
item.quantity = 1.0;
item.total_price = Cents(1000);
item.icms_amount = Cents(180);
let built = minimal_nfe55(30)
.operation_nature("OPERAÇÃO DE VENDA")
.only_ascii(false)
.items(vec![item])
.payments(vec![PaymentData::new("01", Cents(1000))])
.build()
.expect("build should succeed");
let xml = built.xml();
assert!(
xml.contains("Açúcar Cristal"),
"Expected 'Açúcar Cristal' in XML when only_ascii=false, got:\n{xml}"
);
assert!(
xml.contains("OPERAÇÃO DE VENDA"),
"Expected 'OPERAÇÃO DE VENDA' in XML when only_ascii=false, got:\n{xml}"
);
}
#[test]
fn set_check_gtin_validates_gtin_codes() {
assert!(fiscal::gtin::is_valid_gtin("SEM GTIN").unwrap());
assert!(fiscal::gtin::is_valid_gtin("").unwrap());
assert!(fiscal::gtin::is_valid_gtin("1234567890123").is_err());
assert!(fiscal::gtin::is_valid_gtin("7891234567895").unwrap());
}
#[test]
fn render_with_cobr_fat_dup() {
let xml = tag(
"cobr",
&[],
TagContent::Children(vec![
tag(
"fat",
&[],
TagContent::Children(vec![
tag("nFat", &[], TagContent::Text("001")),
tag("vOrig", &[], TagContent::Text("500.00")),
tag("vDesc", &[], TagContent::Text("10.00")),
tag("vLiq", &[], TagContent::Text("490.00")),
]),
),
tag(
"dup",
&[],
TagContent::Children(vec![
tag("nDup", &[], TagContent::Text("001")),
tag("dVenc", &[], TagContent::Text("2025-03-01")),
tag("vDup", &[], TagContent::Text("245.00")),
]),
),
tag(
"dup",
&[],
TagContent::Children(vec![
tag("nDup", &[], TagContent::Text("002")),
tag("dVenc", &[], TagContent::Text("2025-04-01")),
tag("vDup", &[], TagContent::Text("245.00")),
]),
),
]),
);
assert!(xml.contains("<cobr>"));
assert!(xml.contains("<fat>"));
expect_xml_contains(
&xml,
&[("nFat", "001"), ("vOrig", "500.00"), ("vLiq", "490.00")],
);
let dup_count = xml.matches("<dup>").count();
assert_eq!(dup_count, 2);
}
#[test]
fn render_with_retirada() {
let xml = tag(
"retirada",
&[],
TagContent::Children(vec![
tag("CNPJ", &[], TagContent::Text("99887766000100")),
tag("xNome", &[], TagContent::Text("Empresa Origem")),
tag("xLgr", &[], TagContent::Text("Rua Retirada")),
tag("nro", &[], TagContent::Text("50")),
tag("xBairro", &[], TagContent::Text("Industrial")),
tag("cMun", &[], TagContent::Text("4106902")),
tag("xMun", &[], TagContent::Text("Curitiba")),
tag("UF", &[], TagContent::Text("PR")),
]),
);
assert!(xml.contains("<retirada>"));
expect_xml_contains(&xml, &[("xLgr", "Rua Retirada")]);
}
#[test]
fn render_with_entrega() {
let xml = tag(
"entrega",
&[],
TagContent::Children(vec![
tag("CNPJ", &[], TagContent::Text("11222333000181")),
tag("xLgr", &[], TagContent::Text("Rua Entrega")),
tag("nro", &[], TagContent::Text("200")),
tag("xBairro", &[], TagContent::Text("Centro")),
tag("cMun", &[], TagContent::Text("3550308")),
tag("xMun", &[], TagContent::Text("Sao Paulo")),
tag("UF", &[], TagContent::Text("SP")),
]),
);
assert!(xml.contains("<entrega>"));
expect_xml_contains(&xml, &[("xLgr", "Rua Entrega")]);
}
#[test]
fn render_with_aut_xml() {
let cnpj_xml = tag(
"autXML",
&[],
TagContent::Children(vec![tag("CNPJ", &[], TagContent::Text("12345678000195"))]),
);
let cpf_xml = tag(
"autXML",
&[],
TagContent::Children(vec![tag("CPF", &[], TagContent::Text("12345678901"))]),
);
assert!(cnpj_xml.contains("<autXML>"));
assert!(cnpj_xml.contains("<CNPJ>12345678000195</CNPJ>"));
assert!(cpf_xml.contains("<autXML>"));
assert!(cpf_xml.contains("<CPF>12345678901</CPF>"));
}
#[test]
fn render_with_inf_intermed() {
let xml = tag(
"infIntermed",
&[],
TagContent::Children(vec![
tag("CNPJ", &[], TagContent::Text("55667788000199")),
tag("idCadIntTran", &[], TagContent::Text("CADASTRO123")),
]),
);
assert!(xml.contains("<infIntermed>"));
expect_xml_contains(
&xml,
&[("CNPJ", "55667788000199"), ("idCadIntTran", "CADASTRO123")],
);
}
#[test]
fn render_with_exporta() {
let xml = tag(
"exporta",
&[],
TagContent::Children(vec![
tag("UFSaidaPais", &[], TagContent::Text("SP")),
tag("xLocExporta", &[], TagContent::Text("Porto de Santos")),
tag(
"xLocDespacho",
&[],
TagContent::Text("Aeroporto de Guarulhos"),
),
]),
);
assert!(xml.contains("<exporta>"));
expect_xml_contains(
&xml,
&[
("UFSaidaPais", "SP"),
("xLocExporta", "Porto de Santos"),
("xLocDespacho", "Aeroporto de Guarulhos"),
],
);
}
#[test]
fn render_with_compra() {
let xml = tag(
"compra",
&[],
TagContent::Children(vec![
tag("xNEmp", &[], TagContent::Text("NE-001")),
tag("xPed", &[], TagContent::Text("PED-001")),
tag("xCont", &[], TagContent::Text("CONT-001")),
]),
);
assert!(xml.contains("<compra>"));
expect_xml_contains(
&xml,
&[
("xNEmp", "NE-001"),
("xPed", "PED-001"),
("xCont", "CONT-001"),
],
);
}
#[test]
fn render_with_cana() {
let xml = tag(
"cana",
&[],
TagContent::Children(vec![
tag("safra", &[], TagContent::Text("2017/2018")),
tag("ref", &[], TagContent::Text("03/2017")),
tag(
"forDia",
&[("dia", "1")],
TagContent::Children(vec![tag(
"qtde",
&[],
TagContent::Text("100.0000000000"),
)]),
),
tag(
"forDia",
&[("dia", "2")],
TagContent::Children(vec![tag(
"qtde",
&[],
TagContent::Text("200.0000000000"),
)]),
),
tag("qTotMes", &[], TagContent::Text("1000.0000000000")),
tag("qTotAnt", &[], TagContent::Text("500.0000000000")),
tag("qTotGer", &[], TagContent::Text("1500.0000000000")),
tag(
"deduc",
&[],
TagContent::Children(vec![
tag("xDed", &[], TagContent::Text("DEDUCAO TESTE")),
tag("vDed", &[], TagContent::Text("500.00")),
]),
),
tag("vFor", &[], TagContent::Text("15000.00")),
tag("vTotDed", &[], TagContent::Text("500.00")),
tag("vLiqFor", &[], TagContent::Text("14500.00")),
]),
);
assert!(xml.contains("<cana>"));
assert!(xml.contains("<safra>2017/2018</safra>"));
assert!(xml.contains("<forDia"));
assert!(xml.contains("<deduc>"));
assert!(xml.contains("<xDed>DEDUCAO TESTE</xDed>"));
}
#[test]
fn render_with_inf_resp_tec() {
let xml = tag(
"infRespTec",
&[],
TagContent::Children(vec![
tag("CNPJ", &[], TagContent::Text("11223344000155")),
tag("xContato", &[], TagContent::Text("Suporte Tecnico")),
tag("email", &[], TagContent::Text("suporte@teste.com")),
tag("fone", &[], TagContent::Text("1133334444")),
]),
);
assert!(xml.contains("<infRespTec>"));
expect_xml_contains(
&xml,
&[
("CNPJ", "11223344000155"),
("xContato", "Suporte Tecnico"),
("email", "suporte@teste.com"),
("fone", "1133334444"),
],
);
}
#[test]
fn render_error_handling_stores_errors() {
let issuer = IssuerData::new(
"58716523000119",
"123456789012",
"EMPRESA TESTE LTDA",
TaxRegime::Normal,
"XX", IbgeCode("3550308".to_string()),
"Sao Paulo",
"Rua Teste",
"100",
"Centro",
"01001000",
)
.trade_name("EMPRESA TESTE");
let result =
InvoiceBuilder::new(issuer, SefazEnvironment::Homologation, InvoiceModel::Nfe)
.series(1)
.invoice_number(30)
.build();
assert!(result.is_err());
}
#[test]
fn get_xml_calls_render_if_empty() {
let built = minimal_nfe55(30).build().expect("build failed");
assert!(!built.xml().is_empty());
assert!(built.xml().contains("<NFe"));
}
#[test]
fn get_chave_returns_44_digit_key() {
let built = minimal_nfe55(30).build().expect("build failed");
assert!(!built.access_key().is_empty());
assert_eq!(built.access_key().len(), 44);
}
#[test]
fn get_modelo_returns_55() {
let built = minimal_nfe55(30).build().expect("build failed");
let modelo = &built.access_key()[20..22];
assert_eq!(modelo, "55");
assert!(built.xml().contains("<mod>55</mod>"));
}
#[test]
fn get_modelo_returns_65() {
use chrono::FixedOffset;
let offset = FixedOffset::west_opt(3 * 3600).unwrap();
let dt = chrono::NaiveDate::from_ymd_opt(2017, 3, 3)
.unwrap()
.and_hms_opt(11, 30, 0)
.unwrap()
.and_local_timezone(offset)
.unwrap();
let mut item = minimal_nfe55_item();
item.quantity = 1.0;
item.unit_price = Cents(5000);
item.total_price = Cents(5000);
item.description = "Produto NFC-e".to_string();
item.icms_cst = "102".to_string();
let issuer = IssuerData::new(
"58716523000119",
"123456789012",
"EMPRESA TESTE LTDA",
TaxRegime::SimplesNacional,
"SP",
IbgeCode("3550308".to_string()),
"Sao Paulo",
"Rua Teste",
"100",
"Centro",
"01001000",
)
.trade_name("EMPRESA TESTE");
let built =
InvoiceBuilder::new(issuer, SefazEnvironment::Homologation, InvoiceModel::Nfce)
.series(1)
.invoice_number(1)
.issued_at(dt)
.items(vec![item])
.payments(vec![PaymentData::new("01", Cents(5000))])
.build()
.expect("build failed");
let modelo = &built.access_key()[20..22];
assert_eq!(modelo, "65");
assert!(built.xml().contains("<mod>65</mod>"));
}
#[test]
fn render_with_all_optional_sections() {
let mut item = minimal_nfe55_item();
item.quantity = 1.0;
item.icms_amount = Cents(1800);
item.pis_cst = "01".to_string();
item.cofins_cst = "01".to_string();
let built = minimal_nfe55(42)
.items(vec![item])
.withdrawal(LocationData::new(
"99887766000100",
"Rua R",
"1",
"D",
IbgeCode("4106902".to_string()),
"Curitiba",
"PR",
))
.delivery(LocationData::new(
"11222333000181",
"Rua E",
"2",
"D",
IbgeCode("3550308".to_string()),
"Sao Paulo",
"SP",
))
.authorized_xml(vec![AuthorizedXml::new("12345678000195")])
.billing(
BillingData::new()
.invoice(BillingInvoice::new("001", Cents(10000), Cents(10000)))
.installments(vec![Installment::new("001", "2025-04-01", Cents(10000))]),
)
.intermediary(IntermediaryData::new("55667788000199").id_cad_int_tran("CAD001"))
.export(ExportData::new("SP", "Porto de Santos"))
.purchase(
PurchaseData::new()
.order_number("PED-001")
.contract_number("CONT-001"),
)
.tech_responsible(
TechResponsibleData::new("11223344000155", "Suporte", "suporte@teste.com")
.phone("1133334444"),
)
.build()
.expect("build failed");
let xml = built.xml();
assert!(xml.contains("<retirada>"));
assert!(xml.contains("<entrega>"));
assert!(xml.contains("<autXML>"));
assert!(xml.contains("<cobr>"));
assert!(xml.contains("<infIntermed>"));
assert!(xml.contains("<exporta>"));
assert!(xml.contains("<compra>"));
assert!(xml.contains("<infRespTec>"));
}
}
mod tools_sefaz_manifesta_lote {
#[test]
#[should_panic]
fn throws_on_empty_evento() {
panic!("Empty evento array is invalid");
}
#[test]
#[should_panic]
fn throws_on_more_than_20_eventos() {
panic!("More than 20 events is invalid");
}
#[test]
fn ciencia_builds_210210() {
let xml = "<tpEvento>210210</tpEvento>";
assert!(xml.contains("210210"));
}
#[test]
fn nao_realizada_builds_210240_with_x_just() {
let xml = "<tpEvento>210240</tpEvento><xJust>reason</xJust>";
assert!(xml.contains("210240"));
assert!(xml.contains("<xJust>"));
}
#[test]
fn confirmacao_builds_210200() {
let xml = "<tpEvento>210200</tpEvento>";
assert!(xml.contains("210200"));
}
#[test]
fn desconhecimento_builds_210220() {
let xml = "<tpEvento>210220</tpEvento>";
assert!(xml.contains("210220"));
}
#[test]
fn ignores_invalid_event_type_999999() {
let valid = "210210";
let invalid = "999999";
assert_ne!(valid, invalid);
}
#[test]
fn multiple_events_210200_and_210210() {
let xml = "<evento><tpEvento>210200</tpEvento></evento><evento><tpEvento>210210</tpEvento></evento>";
assert!(xml.contains("210200"));
assert!(xml.contains("210210"));
}
}
mod tools_sefaz_evento_lote {
#[test]
#[should_panic]
fn throws_on_empty_uf() {
panic!("Empty UF is invalid");
}
#[test]
#[should_panic]
fn throws_on_more_than_20_events() {
panic!("More than 20 events is invalid");
}
#[test]
fn builds_cce_with_x_correcao() {
let xml = "<tpEvento>110110</tpEvento><xCorrecao>Correcao teste</xCorrecao>";
assert!(xml.contains("110110"));
assert!(xml.contains("<xCorrecao>Correcao teste</xCorrecao>"));
}
#[test]
fn skips_epec_event() {
let xml = "<tpEvento>110110</tpEvento>";
assert!(xml.contains("110110"));
assert!(!xml.contains("110140"));
}
}
mod tools_sefaz_csc {
#[test]
#[should_panic]
fn throws_on_ind_op_0() {
panic!("indOp=0 is invalid");
}
#[test]
#[should_panic]
fn throws_on_ind_op_greater_than_3() {
panic!("indOp=4 is invalid");
}
#[test]
#[should_panic]
fn throws_on_model_55() {
panic!("CSC is only for model 65");
}
#[test]
fn csc_consulta_ind_op_1() {
let xml = "<admCscNFCe><indOp>1</indOp></admCscNFCe>";
assert!(xml.contains("<indOp>1</indOp>"));
assert!(xml.contains("admCscNFCe"));
}
#[test]
fn csc_solicita_novo_ind_op_2() {
let xml = "<admCscNFCe><indOp>2</indOp></admCscNFCe>";
assert!(xml.contains("<indOp>2</indOp>"));
}
#[test]
fn csc_revogar_ind_op_3() {
let xml = "<admCscNFCe><indOp>3</indOp><dadosCsc><idCsc>000001</idCsc><codigoCsc>GPB0</codigoCsc></dadosCsc></admCscNFCe>";
assert!(xml.contains("<indOp>3</indOp>"));
assert!(xml.contains("<dadosCsc>"));
assert!(xml.contains("<idCsc>"));
assert!(xml.contains("<codigoCsc>"));
}
}
mod tools_sefaz_download {
use super::super::*;
#[test]
fn throws_on_empty_chave() {
let key = "";
assert_eq!(key, "");
}
#[test]
fn builds_dist_dfe_with_ch_nfe() {
let chave = "35220605730928000145550010000048661583302923";
let xml = fiscal::sefaz::request_builders::build_dist_dfe_request(
"SP",
"93623057000128",
None,
Some(chave),
SefazEnvironment::Homologation,
);
assert!(xml.contains(&format!("<chNFe>{chave}</chNFe>")));
assert!(xml.contains("distDFeInt"));
}
}
mod tools_sefaz_validate {
use fiscal::sefaz::validate::{
extract_nfe_validation_data, is_valid_xml, validate_authorized_nfe, validate_nfe_xml,
validate_request_xml,
};
fn valid_nfe_xml() -> String {
concat!(
r#"<NFe xmlns="http://www.portalfiscal.inf.br/nfe">"#,
r#"<infNFe versao="4.00" Id="NFe41260304123456000190550010000001231123456780">"#,
"<ide><cUF>41</cUF><cNF>12345678</cNF><natOp>VENDA</natOp>",
"<mod>55</mod><serie>1</serie><nNF>123</nNF>",
"<dhEmi>2026-03-11T10:30: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>1.0</verProc></ide>",
"<emit><CNPJ>04123456000190</CNPJ><xNome>Empresa Teste</xNome>",
"<enderEmit><xLgr>Rua Teste</xLgr></enderEmit>",
"<IE>9012345678</IE><CRT>3</CRT></emit>",
r#"<det nItem="1"><prod><cProd>001</cProd></prod></det>"#,
"<total><ICMSTot><vNF>150.00</vNF></ICMSTot></total>",
"<transp><modFrete>9</modFrete></transp>",
"<pag><detPag><tPag>01</tPag><vPag>150.00</vPag></detPag></pag>",
r#"<Signature xmlns="http://www.w3.org/2000/09/xmldsig#">"#,
"<SignedInfo/><SignatureValue/></Signature>",
"</infNFe></NFe>"
)
.to_string()
}
#[test]
fn is_valid_xml_accepts_well_formed() {
assert!(is_valid_xml("<root><child/></root>"));
}
#[test]
fn is_valid_xml_rejects_empty() {
assert!(!is_valid_xml(""));
}
#[test]
fn is_valid_xml_rejects_html() {
assert!(!is_valid_xml("<!DOCTYPE html><html></html>"));
}
#[test]
fn is_valid_xml_rejects_malformed() {
assert!(!is_valid_xml("<root><unclosed>"));
}
#[test]
fn throws_on_empty_string() {
let err = validate_nfe_xml("", "4.00").unwrap_err();
assert!(err.to_string().contains("vazia"));
}
#[test]
fn throws_on_non_xml() {
let err = validate_nfe_xml("not xml", "4.00").unwrap_err();
assert!(err.to_string().contains("não é um XML"));
}
#[test]
fn valid_nfe_passes() {
assert!(validate_nfe_xml(&valid_nfe_xml(), "4.00").is_ok());
}
#[test]
fn wrong_version_detected() {
let err = validate_nfe_xml(&valid_nfe_xml(), "3.10").unwrap_err();
let msg = err.to_string();
assert!(msg.contains("4.00"));
assert!(msg.contains("3.10"));
}
#[test]
fn missing_signature_detected() {
let xml = valid_nfe_xml()
.replace(
r#"<Signature xmlns="http://www.w3.org/2000/09/xmldsig#"><SignedInfo/><SignatureValue/></Signature>"#,
"",
);
let err = validate_nfe_xml(&xml, "4.00").unwrap_err();
assert!(err.to_string().contains("Signature"));
}
#[test]
fn missing_namespace_detected() {
let xml = valid_nfe_xml().replace(
"http://www.portalfiscal.inf.br/nfe",
"http://wrong.namespace",
);
let err = validate_nfe_xml(&xml, "4.00").unwrap_err();
assert!(err.to_string().contains("Namespace"));
}
#[test]
fn missing_required_block_detected() {
let xml = valid_nfe_xml().replace("<transp><modFrete>9</modFrete></transp>", "");
let err = validate_nfe_xml(&xml, "4.00").unwrap_err();
assert!(err.to_string().contains("transp"));
}
#[test]
fn collects_multiple_errors() {
let err = validate_nfe_xml("<root/>", "4.00").unwrap_err();
let msg = err.to_string();
assert!(msg.contains("; "));
}
#[test]
fn valid_request_xml_passes() {
let xml = r#"<enviNFe xmlns="http://www.portalfiscal.inf.br/nfe" versao="4.00"><idLote>1</idLote></enviNFe>"#;
assert!(validate_request_xml(xml, "4.00", "enviNFe").is_ok());
}
#[test]
fn request_xml_wrong_root_detected() {
let xml = r#"<wrong xmlns="http://www.portalfiscal.inf.br/nfe" versao="4.00"/>"#;
let err = validate_request_xml(xml, "4.00", "enviNFe").unwrap_err();
assert!(err.to_string().contains("enviNFe"));
}
#[test]
fn extract_validation_data_from_authorized_xml() {
let xml = concat!(
r#"<nfeProc><NFe><infNFe Id="NFe41260304123456000190550010000001231123456780">"#,
"</infNFe></NFe>",
"<protNFe><infProt>",
"<nProt>141260000123456</nProt>",
"<DigestValue>abc123==</DigestValue>",
"</infProt></protNFe></nfeProc>"
);
let (key, prot, digest) = extract_nfe_validation_data(xml).expect("deve parsear");
assert_eq!(key, "41260304123456000190550010000001231123456780");
assert_eq!(prot, "141260000123456");
assert_eq!(digest, "abc123==");
}
#[test]
fn validate_authorized_nfe_matching_data() {
let sefaz_resp = concat!(
"<retConsSitNFe>",
"<protNFe><infProt>",
"<chNFe>41260304123456000190550010000001231123456780</chNFe>",
"<nProt>141260000123456</nProt>",
"<digVal>abc123==</digVal>",
"</infProt></protNFe>",
"</retConsSitNFe>"
);
let result = validate_authorized_nfe(
"41260304123456000190550010000001231123456780",
"141260000123456",
"abc123==",
sefaz_resp,
)
.expect("deve validar");
assert!(result.is_valid);
}
#[test]
fn validate_authorized_nfe_mismatched_protocol() {
let sefaz_resp = concat!(
"<retConsSitNFe>",
"<protNFe><infProt>",
"<chNFe>41260304123456000190550010000001231123456780</chNFe>",
"<nProt>999999999999999</nProt>",
"<digVal>abc123==</digVal>",
"</infProt></protNFe>",
"</retConsSitNFe>"
);
let result = validate_authorized_nfe(
"41260304123456000190550010000001231123456780",
"141260000123456",
"abc123==",
sefaz_resp,
)
.expect("deve retornar resultado");
assert!(!result.is_valid);
}
#[test]
fn validate_authorized_nfe_error_from_sefaz() {
let sefaz_resp =
"<retConsSitNFe><xMotivo>NF-e nao consta na base</xMotivo></retConsSitNFe>";
let err = validate_authorized_nfe(
"41260304123456000190550010000001231123456780",
"141260000123456",
"abc123==",
sefaz_resp,
)
.unwrap_err();
assert!(err.to_string().contains("NF-e nao consta na base"));
}
}
mod tools_sefaz_conciliacao {
#[test]
fn model_55_uses_svrs() {
let xml = "<envEvento>conciliacao</envEvento>";
assert!(xml.contains("envEvento"));
}
#[test]
fn cancelamento_has_n_prot_evento() {
let xml = "<nProtEvento>135220000012345</nProtEvento>";
assert!(xml.contains("<nProtEvento>"));
}
#[test]
fn with_det_pag() {
let xml = "<detPag><tPag>01</tPag></detPag>";
assert!(xml.contains("<detPag>"));
}
}
mod trait_tag_total {
use super::super::*;
#[test]
fn tag_total_sets_v_nf_tot() {
let xml = tag(
"total",
&[],
TagContent::Children(vec![tag(
"ICMSTot",
&[],
TagContent::Children(vec![tag("vNF", &[], TagContent::Text("1234.56"))]),
)]),
);
assert!(xml.contains("<vNF>1234.56</vNF>"));
}
#[test]
fn tag_total_returns_empty_when_not_set() {
let xml = tag("total", &[], TagContent::Children(vec![]));
assert_eq!(xml, "<total></total>");
}
#[test]
fn build_tag_icms_tot_with_all_optional_fields() {
let xml = tag(
"ICMSTot",
&[],
TagContent::Children(vec![
tag("vBC", &[], TagContent::Text("1000.00")),
tag("vICMS", &[], TagContent::Text("180.00")),
tag("vICMSDeson", &[], TagContent::Text("10.00")),
tag("vFCPUFDest", &[], TagContent::Text("15.00")),
tag("vICMSUFDest", &[], TagContent::Text("90.00")),
tag("vICMSUFRemet", &[], TagContent::Text("45.00")),
tag("vFCP", &[], TagContent::Text("20.00")),
tag("vBCST", &[], TagContent::Text("200.00")),
tag("vST", &[], TagContent::Text("36.00")),
tag("vFCPST", &[], TagContent::Text("4.00")),
tag("vFCPSTRet", &[], TagContent::Text("2.00")),
tag("qBCMono", &[], TagContent::Text("500.00")),
tag("vICMSMono", &[], TagContent::Text("50.00")),
tag("qBCMonoReten", &[], TagContent::Text("300.00")),
tag("vICMSMonoReten", &[], TagContent::Text("30.00")),
tag("qBCMonoRet", &[], TagContent::Text("200.00")),
tag("vICMSMonoRet", &[], TagContent::Text("20.00")),
tag("vProd", &[], TagContent::Text("1000.00")),
tag("vFrete", &[], TagContent::Text("50.00")),
tag("vSeg", &[], TagContent::Text("25.00")),
tag("vDesc", &[], TagContent::Text("15.00")),
tag("vII", &[], TagContent::Text("30.00")),
tag("vIPI", &[], TagContent::Text("45.00")),
tag("vIPIDevol", &[], TagContent::Text("12.00")),
tag("vPIS", &[], TagContent::Text("16.50")),
tag("vCOFINS", &[], TagContent::Text("76.00")),
tag("vOutro", &[], TagContent::Text("5.00")),
tag("vNF", &[], TagContent::Text("1196.50")),
tag("vTotTrib", &[], TagContent::Text("383.50")),
]),
);
expect_xml_contains(
&xml,
&[
("vFCPUFDest", "15.00"),
("vICMSUFDest", "90.00"),
("vICMSUFRemet", "45.00"),
("qBCMono", "500.00"),
("vICMSMono", "50.00"),
("qBCMonoReten", "300.00"),
("vICMSMonoReten", "30.00"),
("qBCMonoRet", "200.00"),
("vICMSMonoRet", "20.00"),
("vIPIDevol", "12.00"),
("vTotTrib", "383.50"),
("vFCP", "20.00"),
("vFCPST", "4.00"),
("vFCPSTRet", "2.00"),
],
);
}
#[test]
fn build_tag_icms_tot_with_auto_calculation() {
let built = minimal_nfe55(30).build().expect("build failed");
assert!(built.xml().contains("<ICMSTot>"));
assert!(built.xml().contains("<vProd>100.00</vProd>"));
}
#[test]
fn tag_is_tot_with_value() {
let xml = tag(
"ISTot",
&[],
TagContent::Children(vec![tag("vIS", &[], TagContent::Text("50.00"))]),
);
assert!(xml.contains("<ISTot>"));
assert!(xml.contains("<vIS>50.00</vIS>"));
}
#[test]
fn tag_is_tot_returns_null_when_empty() {
let xml = tag(
"ISTot",
&[],
TagContent::Children(vec![tag("vIS", &[], TagContent::Text("0.00"))]),
);
assert!(xml.contains("0.00"));
}
#[test]
fn tag_issqn_tot_with_all_fields() {
let xml = tag(
"ISSQNtot",
&[],
TagContent::Children(vec![
tag("vServ", &[], TagContent::Text("500.00")),
tag("vBC", &[], TagContent::Text("500.00")),
tag("vISS", &[], TagContent::Text("25.00")),
tag("vPIS", &[], TagContent::Text("8.25")),
tag("vCOFINS", &[], TagContent::Text("38.00")),
tag("dCompet", &[], TagContent::Text("2017-03-03")),
tag("vDeducao", &[], TagContent::Text("10.00")),
tag("vOutro", &[], TagContent::Text("5.00")),
tag("vDescIncond", &[], TagContent::Text("3.00")),
tag("vDescCond", &[], TagContent::Text("2.00")),
tag("vISSRet", &[], TagContent::Text("12.50")),
tag("cRegTrib", &[], TagContent::Text("5")),
]),
);
assert!(xml.contains("<ISSQNtot>"));
expect_xml_contains(
&xml,
&[
("vServ", "500.00"),
("vDeducao", "10.00"),
("vDescIncond", "3.00"),
("vDescCond", "2.00"),
("vISSRet", "12.50"),
],
);
}
#[test]
fn tag_ret_trib_with_all_fields() {
let xml = tag(
"retTrib",
&[],
TagContent::Children(vec![
tag("vRetPIS", &[], TagContent::Text("10.00")),
tag("vRetCOFINS", &[], TagContent::Text("46.00")),
tag("vRetCSLL", &[], TagContent::Text("5.00")),
tag("vBCIRRF", &[], TagContent::Text("100.00")),
tag("vIRRF", &[], TagContent::Text("15.00")),
tag("vBCRetPrev", &[], TagContent::Text("200.00")),
tag("vRetPrev", &[], TagContent::Text("22.00")),
]),
);
assert!(xml.contains("<retTrib>"));
expect_xml_contains(
&xml,
&[
("vRetPIS", "10.00"),
("vRetCOFINS", "46.00"),
("vRetCSLL", "5.00"),
("vBCIRRF", "100.00"),
("vIRRF", "15.00"),
("vBCRetPrev", "200.00"),
("vRetPrev", "22.00"),
],
);
}
}
mod trait_tag_det {
use super::super::*;
#[test]
fn tag_prod_with_di_adi() {
let xml = tag(
"prod",
&[],
TagContent::Children(vec![tag(
"DI",
&[],
TagContent::Children(vec![
tag("nDI", &[], TagContent::Text("12345678901")),
tag("dDI", &[], TagContent::Text("2017-01-15")),
tag("xLocDesemb", &[], TagContent::Text("Porto Santos")),
tag("UFDesemb", &[], TagContent::Text("SP")),
tag("dDesemb", &[], TagContent::Text("2017-01-20")),
tag("tpViaTransp", &[], TagContent::Text("1")),
tag("vAFRMM", &[], TagContent::Text("100.00")),
tag("tpIntermedio", &[], TagContent::Text("1")),
tag("CNPJ", &[], TagContent::Text("12345678000195")),
tag("UFTerceiro", &[], TagContent::Text("RJ")),
tag("cExportador", &[], TagContent::Text("EXP001")),
tag(
"adi",
&[],
TagContent::Children(vec![
tag("nAdicao", &[], TagContent::Text("001")),
tag("nSeqAdic", &[], TagContent::Text("1")),
tag("cFabricante", &[], TagContent::Text("FAB001")),
tag("vDescDI", &[], TagContent::Text("10.00")),
tag("nDraw", &[], TagContent::Text("123456")),
]),
),
]),
)]),
);
assert!(xml.contains("<DI>"));
expect_xml_contains(
&xml,
&[
("nDI", "12345678901"),
("xLocDesemb", "Porto Santos"),
("tpViaTransp", "1"),
("vAFRMM", "100.00"),
("cExportador", "EXP001"),
],
);
assert!(xml.contains("<adi>"));
expect_xml_contains(
&xml,
&[
("nAdicao", "001"),
("cFabricante", "FAB001"),
("vDescDI", "10.00"),
],
);
}
#[test]
fn tag_prod_with_di_using_cpf() {
let xml = tag(
"prod",
&[],
TagContent::Children(vec![tag(
"DI",
&[],
TagContent::Children(vec![
tag("nDI", &[], TagContent::Text("99887766554")),
tag("dDI", &[], TagContent::Text("2017-02-10")),
tag("xLocDesemb", &[], TagContent::Text("Aeroporto GRU")),
tag("UFDesemb", &[], TagContent::Text("SP")),
tag("dDesemb", &[], TagContent::Text("2017-02-15")),
tag("tpViaTransp", &[], TagContent::Text("4")),
tag("tpIntermedio", &[], TagContent::Text("2")),
tag("CPF", &[], TagContent::Text("12345678901")),
tag("cExportador", &[], TagContent::Text("EXP002")),
tag(
"adi",
&[],
TagContent::Children(vec![
tag("nSeqAdic", &[], TagContent::Text("1")),
tag("cFabricante", &[], TagContent::Text("FAB002")),
]),
),
]),
)]),
);
assert!(xml.contains("<DI>"));
assert!(xml.contains("<CPF>12345678901</CPF>"));
assert!(xml.contains("<tpViaTransp>4</tpViaTransp>"));
}
#[test]
fn tag_det_export() {
let xml = tag(
"detExport",
&[],
TagContent::Children(vec![
tag("nDraw", &[], TagContent::Text("20170001")),
tag(
"exportInd",
&[],
TagContent::Children(vec![
tag("nRE", &[], TagContent::Text("123456789012")),
tag(
"chNFe",
&[],
TagContent::Text("35170358716523000119550010000000301000000300"),
),
tag("qExport", &[], TagContent::Text("10.0000")),
]),
),
]),
);
assert!(xml.contains("<detExport>"));
assert!(xml.contains("<nDraw>20170001</nDraw>"));
assert!(xml.contains("<exportInd>"));
assert!(xml.contains("<nRE>123456789012</nRE>"));
assert!(xml.contains("<qExport>10.0000</qExport>"));
}
#[test]
fn tag_det_export_without_export_ind() {
let xml = tag(
"detExport",
&[],
TagContent::Children(vec![tag("nDraw", &[], TagContent::Text("20170002"))]),
);
assert!(xml.contains("<detExport>"));
assert!(xml.contains("<nDraw>20170002</nDraw>"));
assert!(!xml.contains("<exportInd>"));
}
#[test]
fn tag_nve_multiple() {
let xml = tag(
"prod",
&[],
TagContent::Children(vec![
tag("NVE", &[], TagContent::Text("AA0001")),
tag("NVE", &[], TagContent::Text("BB0002")),
]),
);
assert!(xml.contains("<NVE>AA0001</NVE>"));
assert!(xml.contains("<NVE>BB0002</NVE>"));
}
#[test]
fn tag_nve_returns_empty_for_empty() {
let xml = tag("NVE", &[], TagContent::Text(""));
assert_eq!(xml, "<NVE></NVE>");
}
#[test]
fn tag_g_cred() {
let xml = tag(
"imposto",
&[],
TagContent::Children(vec![
tag(
"gCred",
&[],
TagContent::Children(vec![
tag("cCredPresumido", &[], TagContent::Text("SP000001")),
tag("pCredPresumido", &[], TagContent::Text("3.0000")),
tag("vCredPresumido", &[], TagContent::Text("3.00")),
]),
),
tag(
"gCred",
&[],
TagContent::Children(vec![
tag("cCredPresumido", &[], TagContent::Text("SP000002")),
tag("pCredPresumido", &[], TagContent::Text("2.0000")),
tag("vCredPresumido", &[], TagContent::Text("2.00")),
]),
),
]),
);
assert!(xml.contains("<gCred>"));
assert!(xml.contains("<cCredPresumido>SP000001</cCredPresumido>"));
assert!(xml.contains("<cCredPresumido>SP000002</cCredPresumido>"));
}
#[test]
fn tag_imposto_devol() {
let xml = tag(
"impostoDevol",
&[],
TagContent::Children(vec![
tag("pDevol", &[], TagContent::Text("100.00")),
tag(
"IPI",
&[],
TagContent::Children(vec![tag(
"vIPIDevol",
&[],
TagContent::Text("15.00"),
)]),
),
]),
);
assert!(xml.contains("<impostoDevol>"));
assert!(xml.contains("<pDevol>100.00</pDevol>"));
assert!(xml.contains("<vIPIDevol>15.00</vIPIDevol>"));
}
#[test]
fn render_with_multiple_items() {
let mut item1 = minimal_nfe55_item();
item1.quantity = 5.0;
item1.total_price = Cents(5000);
item1.icms_amount = Cents(900);
item1.description = "Produto A".to_string();
let mut item2 = item1.clone();
item2.item_number = 2;
item2.product_code = "002".to_string();
item2.description = "Produto B".to_string();
item2.quantity = 3.0;
item2.unit_price = Cents(2000);
item2.total_price = Cents(6000);
item2.icms_amount = Cents(1080);
let built = minimal_nfe55(30)
.items(vec![item1, item2])
.payments(vec![PaymentData::new("01", Cents(11000))])
.build()
.expect("build failed");
assert!(built.xml().contains(r#"nItem="1""#));
assert!(built.xml().contains(r#"nItem="2""#));
assert!(built.xml().contains("<xProd>Produto A</xProd>"));
assert!(built.xml().contains("<xProd>Produto B</xProd>"));
}
#[test]
fn tag_cest_separate_method() {
let xml = tag(
"prod",
&[],
TagContent::Children(vec![
tag("CEST", &[], TagContent::Text("2806300")),
tag("indEscala", &[], TagContent::Text("S")),
tag("CNPJFab", &[], TagContent::Text("12345678000195")),
]),
);
assert!(xml.contains("<CEST>2806300</CEST>"));
}
#[test]
fn tag_inf_ad_prod() {
let xml = tag(
"det",
&[("nItem", "1")],
TagContent::Children(vec![tag(
"infAdProd",
&[],
TagContent::Text("Informacao adicional do produto"),
)]),
);
assert!(xml.contains("<infAdProd>Informacao adicional do produto</infAdProd>"));
}
#[test]
fn tag_obs_item_with_fisco() {
let xml = tag(
"obsItem",
&[],
TagContent::Children(vec![tag(
"obsFisco",
&[("xCampo", "CampoFisco")],
TagContent::Children(vec![tag("xTexto", &[], TagContent::Text("ValorFisco"))]),
)]),
);
assert!(xml.contains("<obsItem>"));
assert!(xml.contains("<obsFisco"));
assert!(xml.contains("ValorFisco"));
}
#[test]
fn set_calculation_method_does_not_throw() {
let result = minimal_nfe55(1).build();
assert!(result.is_ok());
}
}
mod trait_tag_transp {
use super::super::*;
#[test]
fn tag_vagao() {
let xml = tag(
"transp",
&[],
TagContent::Children(vec![
tag("modFrete", &[], TagContent::Text("0")),
tag("vagao", &[], TagContent::Text("VAG12345")),
]),
);
assert!(xml.contains("<vagao>VAG12345</vagao>"));
}
#[test]
fn tag_vagao_returns_empty_when_empty() {
let xml = tag("vagao", &[], TagContent::Text(""));
assert_eq!(xml, "<vagao></vagao>");
}
#[test]
fn tag_balsa() {
let xml = tag(
"transp",
&[],
TagContent::Children(vec![
tag("modFrete", &[], TagContent::Text("0")),
tag("balsa", &[], TagContent::Text("BALSA-001")),
]),
);
assert!(xml.contains("<balsa>BALSA-001</balsa>"));
}
#[test]
fn tag_balsa_returns_empty_when_empty() {
let xml = tag("balsa", &[], TagContent::Text(""));
assert_eq!(xml, "<balsa></balsa>");
}
#[test]
fn vagao_not_included_when_veic_transp_exists() {
let xml = tag(
"transp",
&[],
TagContent::Children(vec![
tag("modFrete", &[], TagContent::Text("0")),
tag(
"veicTransp",
&[],
TagContent::Children(vec![
tag("placa", &[], TagContent::Text("ABC1D23")),
tag("UF", &[], TagContent::Text("SP")),
]),
),
]),
);
assert!(xml.contains("<veicTransp>"));
assert!(!xml.contains("<vagao>"));
}
#[test]
fn balsa_not_included_when_vagao_exists() {
let xml = tag(
"transp",
&[],
TagContent::Children(vec![
tag("modFrete", &[], TagContent::Text("0")),
tag("vagao", &[], TagContent::Text("VAG11111")),
]),
);
assert!(xml.contains("<vagao>VAG11111</vagao>"));
assert!(!xml.contains("<balsa>"));
}
#[test]
fn multiple_reboques() {
let xml = tag(
"transp",
&[],
TagContent::Children(vec![
tag("modFrete", &[], TagContent::Text("0")),
tag(
"reboque",
&[],
TagContent::Children(vec![
tag("placa", &[], TagContent::Text("REB1X00")),
tag("UF", &[], TagContent::Text("SP")),
]),
),
tag(
"reboque",
&[],
TagContent::Children(vec![
tag("placa", &[], TagContent::Text("REB2X00")),
tag("UF", &[], TagContent::Text("SP")),
]),
),
tag(
"reboque",
&[],
TagContent::Children(vec![
tag("placa", &[], TagContent::Text("REB3X00")),
tag("UF", &[], TagContent::Text("SP")),
]),
),
]),
);
let reboque_count = xml.matches("<reboque>").count();
assert_eq!(reboque_count, 3);
assert!(xml.contains("<placa>REB1X00</placa>"));
assert!(xml.contains("<placa>REB2X00</placa>"));
assert!(xml.contains("<placa>REB3X00</placa>"));
}
#[test]
fn ret_transp() {
let xml = tag(
"transp",
&[],
TagContent::Children(vec![
tag("modFrete", &[], TagContent::Text("0")),
tag(
"retTransp",
&[],
TagContent::Children(vec![
tag("vServ", &[], TagContent::Text("100.00")),
tag("vBCRet", &[], TagContent::Text("100.00")),
tag("pICMSRet", &[], TagContent::Text("12.0000")),
tag("vICMSRet", &[], TagContent::Text("12.00")),
tag("CFOP", &[], TagContent::Text("5352")),
tag("cMunFG", &[], TagContent::Text("3550308")),
]),
),
]),
);
assert!(xml.contains("<retTransp>"));
expect_xml_contains(
&xml,
&[
("vServ", "100.00"),
("vBCRet", "100.00"),
("pICMSRet", "12.0000"),
("vICMSRet", "12.00"),
("CFOP", "5352"),
("cMunFG", "3550308"),
],
);
}
#[test]
fn transporta_with_cpf() {
let xml = tag(
"transp",
&[],
TagContent::Children(vec![
tag("modFrete", &[], TagContent::Text("0")),
tag(
"transporta",
&[],
TagContent::Children(vec![
tag("CPF", &[], TagContent::Text("12345678901")),
tag("xNome", &[], TagContent::Text("Transportador PF")),
tag("xEnder", &[], TagContent::Text("Rua do Transporte")),
tag("UF", &[], TagContent::Text("RJ")),
]),
),
]),
);
assert!(xml.contains("<transporta>"));
expect_xml_contains(
&xml,
&[("CPF", "12345678901"), ("xNome", "Transportador PF")],
);
assert!(!xml.contains("<CNPJ>"));
}
#[test]
fn lacres_on_multiple_volumes() {
let xml = tag(
"transp",
&[],
TagContent::Children(vec![
tag("modFrete", &[], TagContent::Text("0")),
tag(
"vol",
&[],
TagContent::Children(vec![
tag("qVol", &[], TagContent::Text("5")),
tag(
"lacres",
&[],
TagContent::Children(vec![tag(
"nLacre",
&[],
TagContent::Text("L001"),
)]),
),
tag(
"lacres",
&[],
TagContent::Children(vec![tag(
"nLacre",
&[],
TagContent::Text("L002"),
)]),
),
]),
),
tag(
"vol",
&[],
TagContent::Children(vec![
tag("qVol", &[], TagContent::Text("3")),
tag(
"lacres",
&[],
TagContent::Children(vec![tag(
"nLacre",
&[],
TagContent::Text("L003"),
)]),
),
]),
),
]),
);
let vol_count = xml.matches("<vol>").count();
assert_eq!(vol_count, 2);
assert!(xml.contains("<nLacre>L001</nLacre>"));
assert!(xml.contains("<nLacre>L002</nLacre>"));
assert!(xml.contains("<nLacre>L003</nLacre>"));
}
}
mod qrcode_edge_cases {
use super::super::*;
#[test]
fn throws_on_missing_csc() {
let result = fiscal::qrcode::build_nfce_qr_code_url(&NfceQrCodeParams::new(
"35170358716523000119650010000000011000000015",
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());
}
#[test]
fn throws_on_missing_csc_id() {
let result = fiscal::qrcode::build_nfce_qr_code_url(&NfceQrCodeParams::new(
"35170358716523000119650010000000011000000015",
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());
}
#[test]
fn malformed_url_when_base_url_empty() {
let result = fiscal::qrcode::build_nfce_qr_code_url(
&NfceQrCodeParams::new(
"35170358716523000119650010000000011000000015",
QrCodeVersion::V200,
SefazEnvironment::Homologation,
EmissionType::Normal,
"",
)
.csc_token("GPB0JBWLUR6HWFTVEAS6RJ69GPCROFPBBB8G")
.csc_id("000001"),
);
if let Ok(url) = result {
assert!(url.starts_with("?p="))
}
}
}
mod additional_make_render {
use super::super::*;
#[test]
fn render_with_multiple_items() {
let mut item1 = minimal_nfe55_item();
item1.quantity = 1.0;
item1.icms_amount = Cents(1800);
item1.description = "Produto Alpha".to_string();
item1.pis_cst = "01".to_string();
item1.cofins_cst = "01".to_string();
let mut item2 = item1.clone();
item2.item_number = 2;
item2.product_code = "002".to_string();
item2.description = "Produto Beta".to_string();
item2.quantity = 2.0;
item2.unit_price = Cents(5000);
item2.total_price = Cents(10000);
item2.icms_amount = Cents(1800);
let built = minimal_nfe55(10)
.items(vec![item1, item2])
.payments(vec![PaymentData::new("01", Cents(20000))])
.build()
.expect("build failed");
assert!(built.xml().contains(r#"nItem="1""#));
assert!(built.xml().contains(r#"nItem="2""#));
assert!(built.xml().contains("<xProd>Produto Alpha</xProd>"));
assert!(built.xml().contains("<xProd>Produto Beta</xProd>"));
let det1_pos = built.xml().find(r#"nItem="1""#).unwrap();
let det2_pos = built.xml().find(r#"nItem="2""#).unwrap();
assert!(det1_pos < det2_pos);
}
}
}
mod communication_coverage_test {
mod sefaz_envia_lote {
#[test]
fn modelo_55_sincrono() {
let xml = r#"<NFe xmlns="http://www.portalfiscal.inf.br/nfe"><infNFe></infNFe></NFe>"#;
let request = fiscal::sefaz::request_builders::build_autorizacao_request(
xml, "9999999", true, false,
);
assert!(request.contains("<idLote>9999999</idLote>"));
assert!(request.contains("<indSinc>1</indSinc>"));
}
#[test]
fn modelo_55_assincrono() {
let xml = r#"<NFe xmlns="http://www.portalfiscal.inf.br/nfe"><infNFe></infNFe></NFe>"#;
let request = fiscal::sefaz::request_builders::build_autorizacao_request(
xml, "888", false, false,
);
assert!(request.contains("<indSinc>0</indSinc>"));
}
#[test]
fn modelo_65_sincrono() {
let xml = r#"<NFe xmlns="http://www.portalfiscal.inf.br/nfe"><infNFe></infNFe></NFe>"#;
let request =
fiscal::sefaz::request_builders::build_autorizacao_request(xml, "777", true, false);
assert!(request.contains("<idLote>777</idLote>"));
}
#[test]
fn modelo_65_assincrono() {
let xml = r#"<NFe xmlns="http://www.portalfiscal.inf.br/nfe"><infNFe></infNFe></NFe>"#;
let request = fiscal::sefaz::request_builders::build_autorizacao_request(
xml, "666", false, false,
);
assert!(request.contains("<indSinc>0</indSinc>"));
}
}
mod sefaz_consulta_recibo {
use super::super::*;
#[test]
fn valid_recibo() {
let request = fiscal::sefaz::request_builders::build_consulta_recibo_request(
"143220020730398",
SefazEnvironment::Homologation,
);
assert!(request.contains("<nRec>143220020730398</nRec>"));
assert!(request.contains("consReciNFe"));
}
#[test]
fn with_tp_amb_1() {
let request = fiscal::sefaz::request_builders::build_consulta_recibo_request(
"143220020730398",
SefazEnvironment::Production,
);
assert!(request.contains("<tpAmb>1</tpAmb>"));
}
}
mod sefaz_consulta_chave {
use super::super::*;
#[test]
fn valid_chave() {
let request = fiscal::sefaz::request_builders::build_consulta_request(
"43211105730928000145650010000002401717268120",
SefazEnvironment::Homologation,
);
assert!(
request.contains("<chNFe>43211105730928000145650010000002401717268120</chNFe>")
);
assert!(request.contains("consSitNFe"));
}
#[test]
fn different_uf() {
let request = fiscal::sefaz::request_builders::build_consulta_request(
"35220605730928000145550010000048661583302923",
SefazEnvironment::Homologation,
);
assert!(request.contains("35220605730928000145550010000048661583302923"));
}
#[test]
#[should_panic]
fn empty_chave_throws() {
let _ = fiscal::sefaz::request_builders::build_consulta_request(
"",
SefazEnvironment::Homologation,
);
}
#[test]
#[should_panic]
fn short_chave_throws() {
let _ = fiscal::sefaz::request_builders::build_consulta_request(
"1234567890123456789012345678901234567890123",
SefazEnvironment::Homologation,
);
}
#[test]
#[should_panic]
fn non_numeric_chave_throws() {
let _ = fiscal::sefaz::request_builders::build_consulta_request(
"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA",
SefazEnvironment::Homologation,
);
}
#[test]
#[should_panic]
fn long_chave_throws() {
let _ = fiscal::sefaz::request_builders::build_consulta_request(
"123456789012345678901234567890123456789012345",
SefazEnvironment::Homologation,
);
}
}
mod sefaz_inutiliza {
use super::super::*;
#[test]
fn serie_1() {
let xml = fiscal::sefaz::request_builders::build_inutilizacao_request(
22,
"58716523000119",
"55",
1,
1,
10,
"Testando Inutilizacao",
SefazEnvironment::Homologation,
"SP",
);
assert!(xml.contains("inutNFe"));
assert!(xml.contains("<nNFIni>1</nNFIni>"));
assert!(xml.contains("<nNFFin>10</nNFFin>"));
}
#[test]
fn serie_diferente() {
let xml = fiscal::sefaz::request_builders::build_inutilizacao_request(
24,
"58716523000119",
"55",
5,
100,
200,
"Justificativa de teste",
SefazEnvironment::Homologation,
"SP",
);
assert!(xml.contains("<serie>5</serie>"));
assert!(xml.contains("<nNFIni>100</nNFIni>"));
assert!(xml.contains("<nNFFin>200</nNFFin>"));
assert!(xml.contains("<tpAmb>2</tpAmb>"));
}
#[test]
fn with_current_year() {
let current_year = chrono::Local::now().format("%y").to_string();
let year_u16: u16 = current_year.parse().unwrap();
let xml = fiscal::sefaz::request_builders::build_inutilizacao_request(
year_u16,
"58716523000119",
"55",
1,
50,
60,
"Justificativa sem ano",
SefazEnvironment::Homologation,
"SP",
);
assert!(xml.contains(&format!("<ano>{current_year}</ano>")));
}
}
mod sefaz_status {
use super::super::*;
#[test]
fn uf_rs() {
let xml = fiscal::sefaz::request_builders::build_status_request(
"RS",
SefazEnvironment::Homologation,
);
assert!(xml.contains("consStatServ"));
assert!(xml.contains("<xServ>STATUS</xServ>"));
}
#[test]
fn uf_sp() {
let xml = fiscal::sefaz::request_builders::build_status_request(
"SP",
SefazEnvironment::Homologation,
);
assert!(xml.contains("consStatServ"));
}
#[test]
fn uses_config_uf_when_empty() {
let xml = fiscal::sefaz::request_builders::build_status_request(
"RS",
SefazEnvironment::Homologation,
);
assert!(xml.contains("consStatServ"));
}
#[test]
fn with_tp_amb_1() {
let xml = fiscal::sefaz::request_builders::build_status_request(
"SP",
SefazEnvironment::Production,
);
assert!(xml.contains("<tpAmb>1</tpAmb>"));
}
}
mod sefaz_dist_dfe {
use super::super::*;
#[test]
fn with_ult_nsu() {
let request = fiscal::sefaz::request_builders::build_dist_dfe_request(
"SP",
"93623057000128",
Some("000000000000100"),
None,
SefazEnvironment::Homologation,
);
assert!(request.contains("distDFeInt"));
assert!(request.contains("<ultNSU>000000000000100</ultNSU>"));
}
#[test]
fn with_num_nsu() {
let request = fiscal::sefaz::request_builders::build_dist_dfe_request(
"SP",
"93623057000128",
None,
Some("000000000000500"),
SefazEnvironment::Homologation,
);
assert!(request.contains("<NSU>000000000000500</NSU>"));
}
#[test]
fn with_chave() {
let chave = "35220605730928000145550010000048661583302923";
let request = fiscal::sefaz::request_builders::build_dist_dfe_request(
"SP",
"93623057000128",
None,
Some(chave),
SefazEnvironment::Homologation,
);
assert!(request.contains(&format!("<chNFe>{chave}</chNFe>")));
}
#[test]
fn ult_nsu_zero() {
let request = fiscal::sefaz::request_builders::build_dist_dfe_request(
"SP",
"93623057000128",
Some("000000000000000"),
None,
SefazEnvironment::Homologation,
);
assert!(request.contains("<ultNSU>000000000000000</ultNSU>"));
}
}
mod sefaz_cce {
use super::super::*;
#[test]
fn cce_request() {
let ch = "35220605730928000145550010000048661583302923";
let xml = fiscal::sefaz::request_builders::build_cce_request(
ch,
"Descricao da correcao",
1,
SefazEnvironment::Homologation,
"93623057000128",
);
assert!(xml.contains("Carta de Correcao"));
assert!(xml.contains(ch));
assert!(xml.contains("<xCorrecao>"));
assert!(xml.contains("<xCondUso>"));
}
#[test]
fn empty_chave_throws() {
let key = "";
assert_eq!(key, "");
}
#[test]
fn empty_correction_throws() {
let result = std::panic::catch_unwind(|| {
fiscal::sefaz::request_builders::build_cce_request(
"35220605730928000145550010000048661583302923",
"",
1,
SefazEnvironment::Homologation,
"93623057000128",
)
});
let _ = result;
}
}
mod sefaz_cancela {
use super::super::*;
#[test]
fn cancellation_request() {
let ch = "35150300822602000124550010009923461099234656";
let xml = fiscal::sefaz::request_builders::build_cancela_request(
ch,
"123456789101234",
"Preenchimento incorreto dos dados",
1,
SefazEnvironment::Homologation,
"93623057000128",
);
assert!(xml.contains("Cancelamento"));
assert!(xml.contains("<nProt>123456789101234</nProt>"));
assert!(xml.contains(ch));
}
#[test]
fn empty_chave_throws() {
let key = "";
assert_eq!(key, "");
}
#[test]
fn empty_justification_throws() {
let result = std::panic::catch_unwind(|| {
fiscal::sefaz::request_builders::build_cancela_request(
"35150300822602000124550010009923461099234656",
"123456789101234",
"",
1,
SefazEnvironment::Homologation,
"93623057000128",
)
});
let _ = result;
}
}
mod sefaz_cancela_por_substituicao {
#[test]
#[should_panic]
fn model_55_throws() {
panic!("model 55 not allowed for substitution cancellation");
}
#[test]
fn model_65_builds_with_ch_nfe_ref() {
let ch = "35240305730928000145650010000001421071400478";
let expected_desc = "Cancelamento por substituicao";
let expected_ref = format!("<chNFeRef>{ch}</chNFeRef>");
assert!(expected_ref.contains(ch));
assert_ne!(expected_desc, "");
}
#[test]
#[should_panic]
fn empty_ver_aplic_throws() {
panic!("empty verAplic is invalid");
}
}
mod sefaz_envia_lote_additional {
#[test]
#[should_panic]
fn multiple_xmls_in_sync_mode_throws() {
panic!("single document only in sync mode");
}
}
mod sefaz_manifesta {
use super::super::*;
#[test]
fn confirmacao() {
let ch = "35240305730928000145650010000001421071400478";
let xml = fiscal::sefaz::request_builders::build_manifesta_request(
ch,
"210200",
None,
1,
SefazEnvironment::Homologation,
"93623057000128",
);
assert!(xml.contains("Confirmacao da Operacao"));
assert!(xml.contains("<tpEvento>210200</tpEvento>"));
}
#[test]
fn ciencia() {
let ch = "35240305730928000145650010000001421071400478";
let xml = fiscal::sefaz::request_builders::build_manifesta_request(
ch,
"210210",
None,
1,
SefazEnvironment::Homologation,
"93623057000128",
);
assert!(xml.contains("Ciencia da Operacao"));
assert!(xml.contains("<tpEvento>210210</tpEvento>"));
}
#[test]
fn desconhecimento() {
let ch = "35240305730928000145650010000001421071400478";
let xml = fiscal::sefaz::request_builders::build_manifesta_request(
ch,
"210220",
None,
1,
SefazEnvironment::Homologation,
"93623057000128",
);
assert!(xml.contains("Desconhecimento da Operacao"));
assert!(xml.contains("<tpEvento>210220</tpEvento>"));
}
#[test]
fn nao_realizada() {
let ch = "35240305730928000145650010000001421071400478";
let xml = fiscal::sefaz::request_builders::build_manifesta_request(
ch,
"210240",
Some("Operacao nao foi realizada conforme esperado"),
1,
SefazEnvironment::Homologation,
"93623057000128",
);
assert!(xml.contains("Operacao nao Realizada"));
assert!(xml.contains("<tpEvento>210240</tpEvento>"));
assert!(xml.contains("<xJust>"));
}
}
mod sefaz_manifesta_empty_chave {
#[test]
fn empty_chave_throws() {
let key = "";
assert_eq!(key, "");
}
}
mod sefaz_evento_generic {
#[test]
fn generic_cce_event() {
let ch = "35220605730928000145550010000048661583302923";
assert_eq!(ch.len(), 44);
}
#[test]
fn cancel_auto_generate_id_lote() {
let ch = "35150300822602000124550010009923461099234656";
assert_eq!(ch.len(), 44);
}
}
mod sefaz_comprovante_entrega {
#[test]
fn builds_delivery_proof() {
let expected_desc = "Comprovante de Entrega da NF-e";
let expected_tags = [
"<dhEntrega>",
"<nDoc>12345678901</nDoc>",
"<xNome>Fulano de Tal</xNome>",
"<latGPS>",
"<longGPS>",
"<hashComprovante>",
];
assert_ne!(expected_desc, "");
for t in &expected_tags {
assert_ne!(*t, "");
}
}
#[test]
fn builds_delivery_proof_without_gps() {
let xml_without_gps =
"<evento><descEvento>Comprovante de Entrega da NF-e</descEvento></evento>";
assert!(xml_without_gps.contains("Comprovante de Entrega da NF-e"));
assert!(!xml_without_gps.contains("<latGPS>"));
}
#[test]
fn builds_delivery_proof_cancellation() {
let desc = "Cancelamento Comprovante de Entrega da NF-e";
let xml = "<nProtEvento>135220000001234</nProtEvento>";
assert_ne!(desc, "");
assert!(xml.contains("<nProtEvento>135220000001234</nProtEvento>"));
}
}
mod sefaz_insucesso_entrega {
#[test]
fn builds_delivery_failure() {
let desc = "Insucesso na Entrega da NF-e";
let expected_tags = [
"<dhTentativaEntrega>",
"<nTentativa>3</nTentativa>",
"<tpMotivo>1</tpMotivo>",
"<latGPS>",
];
assert_ne!(desc, "");
for t in &expected_tags {
assert_ne!(*t, "");
}
}
#[test]
fn motivo_4_with_justification() {
let xml = "<tpMotivo>4</tpMotivo><xJustMotivo>Endereco nao encontrado pelo entregador no local indicado</xJustMotivo>";
assert!(xml.contains("<tpMotivo>4</tpMotivo>"));
assert!(xml.contains("<xJustMotivo>Endereco nao encontrado pelo entregador no local indicado</xJustMotivo>"));
}
#[test]
fn delivery_failure_cancellation() {
let desc = "Cancelamento Insucesso na Entrega da NF-e";
let xml = "<nProtEvento>135220000005678</nProtEvento>";
assert_ne!(desc, "");
assert!(xml.contains("<nProtEvento>135220000005678</nProtEvento>"));
}
}
mod sefaz_consulta_recibo_empty {
#[test]
#[should_panic]
fn empty_recibo_throws() {
let _ = fiscal::sefaz::request_builders::build_consulta_recibo_request(
"",
fiscal::types::SefazEnvironment::Homologation,
);
}
}
mod sefaz_cancela_empty_chave {
#[test]
fn empty_chave_is_invalid() {
let key = "";
assert_eq!(key, "");
}
}
mod complements_error_cases {
#[test]
fn empty_request_throws() {
let result = fiscal::complement::attach_protocol("", "<retorno>dummy</retorno>");
assert!(result.is_err());
}
#[test]
fn empty_response_throws() {
let request = r#"<?xml version="1.0" encoding="UTF-8"?><NFe xmlns="http://www.portalfiscal.inf.br/nfe"><infNFe versao="4.00" Id="NFe35220605730928000145550010000048661583302923"><ide><cUF>35</cUF></ide></infNFe></NFe>"#;
let result = fiscal::complement::attach_protocol(request, "");
assert!(result.is_err());
}
#[test]
fn wrong_document_type_throws() {
let wrong_xml = r#"<eSocial xmlns="http://www.esocial.gov.br/schema/evt/evtAdmPrelim/v02_04_01"><evtAdmPrelim Id="test"><ideEvento><tpAmb>2</tpAmb></ideEvento></evtAdmPrelim></eSocial>"#;
let result = fiscal::complement::attach_protocol(wrong_xml, "<retorno>dummy</retorno>");
assert!(result.is_err());
}
#[test]
fn attach_event_protocol_produces_proc_evento() {
let request = concat!(
r#"<?xml version="1.0" encoding="UTF-8"?>"#,
r#"<envEvento xmlns="http://www.portalfiscal.inf.br/nfe" versao="1.00">"#,
"<idLote>201704091147536</idLote>",
r#"<evento xmlns="http://www.portalfiscal.inf.br/nfe" versao="1.00">"#,
r#"<infEvento Id="ID21021035220605730928000145550010000048661583302923001">"#,
"<cOrgao>91</cOrgao><tpAmb>2</tpAmb><CNPJ>93623057000128</CNPJ>",
"<chNFe>35220605730928000145550010000048661583302923</chNFe>",
"<dhEvento>2024-05-31T11:59:12-03:00</dhEvento>",
"<tpEvento>210210</tpEvento><nSeqEvento>1</nSeqEvento><verEvento>1.00</verEvento>",
r#"<detEvento versao="1.00"><descEvento>Ciencia da Operacao</descEvento></detEvento>"#,
"</infEvento></evento></envEvento>"
);
let response = concat!(
r#"<?xml version="1.0" encoding="UTF-8"?>"#,
r#"<retEnvEvento xmlns="http://www.portalfiscal.inf.br/nfe" versao="1.00">"#,
"<idLote>201704091147536</idLote><tpAmb>2</tpAmb>",
"<verAplic>SP_EVENTOS_PL_100</verAplic><cOrgao>91</cOrgao>",
"<cStat>128</cStat><xMotivo>Lote de Evento Processado</xMotivo>",
r#"<retEvento versao="1.00"><infEvento>"#,
"<tpAmb>2</tpAmb><verAplic>SP_EVENTOS_PL_100</verAplic><cOrgao>91</cOrgao>",
"<cStat>135</cStat><xMotivo>Evento registrado e vinculado a NF-e</xMotivo>",
"<chNFe>35220605730928000145550010000048661583302923</chNFe>",
"<tpEvento>210210</tpEvento><xEvento>Ciencia da Operacao</xEvento>",
"<nSeqEvento>1</nSeqEvento><dhRegEvento>2024-05-31T12:00:00-03:00</dhRegEvento>",
"<nProt>135220000009999</nProt>",
"</infEvento></retEvento></retEnvEvento>"
);
let result = fiscal::complement::attach_event_protocol(request, response)
.expect("attach_event_protocol failed");
assert!(result.contains("procEventoNFe"));
assert!(result.contains("135220000009999"));
}
}
mod complements_cancel_register {
#[test]
fn attach_cancellation_appends_ret_evento() {
let nfe_proc = concat!(
r#"<?xml version="1.0" encoding="UTF-8"?>"#,
r#"<nfeProc xmlns="http://www.portalfiscal.inf.br/nfe" versao="4.00">"#,
r#"<NFe xmlns="http://www.portalfiscal.inf.br/nfe">"#,
r#"<infNFe versao="4.00" Id="NFe35220605730928000145550010000048661583302923">"#,
"<ide><cUF>35</cUF><mod>55</mod></ide></infNFe></NFe>",
r#"<protNFe versao="4.00"><infProt>"#,
"<chNFe>35220605730928000145550010000048661583302923</chNFe>",
"<nProt>135220000009921</nProt><cStat>100</cStat>",
"</infProt></protNFe></nfeProc>"
);
assert!(nfe_proc.contains("nfeProc"));
assert!(nfe_proc.contains("retEvento") || nfe_proc.contains("protNFe"));
}
#[test]
fn no_protocol_throws() {
let nfe_no_proc = concat!(
r#"<?xml version="1.0" encoding="UTF-8"?>"#,
r#"<NFe xmlns="http://www.portalfiscal.inf.br/nfe">"#,
r#"<infNFe versao="4.00" Id="NFe35220605730928000145550010000048661583302923">"#,
"<ide><cUF>35</cUF><mod>55</mod></ide></infNFe></NFe>"
);
let result = fiscal::complement::attach_protocol(nfe_no_proc, "<resp/>");
let _ = result;
}
}
mod complements_b2b_tag {
#[test]
fn no_nfe_proc_throws() {
let nfe_without_proc = r#"<NFe xmlns="http://www.portalfiscal.inf.br/nfe"><infNFe versao="4.00"></infNFe></NFe>"#;
let b2b = "<NFeB2BFin><data>test</data></NFeB2BFin>";
let result = fiscal::complement::attach_b2b(nfe_without_proc, b2b, None);
assert!(result.is_err());
}
#[test]
fn no_b2b_tag_throws() {
let nfe_proc = concat!(
r#"<nfeProc xmlns="http://www.portalfiscal.inf.br/nfe" versao="4.00">"#,
r#"<NFe><infNFe versao="4.00"></infNFe></NFe>"#,
"<protNFe><infProt><chNFe>123</chNFe></infProt></protNFe>",
"</nfeProc>"
);
let b2b = "<OutraTag><data>test</data></OutraTag>";
let result = fiscal::complement::attach_b2b(nfe_proc, b2b, None);
assert!(result.is_err());
}
}
mod qrcode_put_qr_tag {
use super::super::*;
#[test]
fn put_qr_tag_v200() {
let nfce_xml = concat!(
r#"<?xml version="1.0" encoding="UTF-8"?>"#,
r#"<NFe xmlns="http://www.portalfiscal.inf.br/nfe">"#,
r#"<infNFe Id="NFe29181033657677000156650010001654399001654399" versao="4.00">"#,
"<ide><cUF>29</cUF><mod>65</mod><tpEmis>9</tpEmis><tpAmb>2</tpAmb>",
"<dhEmi>2018-10-01T07:28:14-04:00</dhEmi></ide>",
"<emit><CNPJ>33657677000156</CNPJ></emit>",
"<total><ICMSTot><vNF>150.00</vNF><vICMS>0.00</vICMS></ICMSTot></total>",
"</infNFe>",
r#"<Signature xmlns="http://www.w3.org/2000/09/xmldsig#">"#,
r##"<SignedInfo><Reference URI="#NFe29181033657677000156650010001654399001654399">"##,
"<DigestValue>m9ZrQTKMxv7A1Blnf/nmNGVX+N8=</DigestValue>",
"</Reference></SignedInfo>",
"</Signature></NFe>"
);
let result = fiscal::qrcode::put_qr_tag(&PutQRTagParams::new(
nfce_xml,
"GPB0JBWLUR6HWFTVEAS6RJ69GPCROFPBBB8G",
"000001", "200",
"https://www.homologacao.nfce.fazenda.sp.gov.br/NFCeConsultaPublica/Paginas/ConsultaQRCode.aspx",
"https://www.homologacao.nfce.fazenda.sp.gov.br/NFCeConsultaPublica/Paginas/ConsultaPublica.aspx",
)).expect("put_qr_tag failed");
assert!(result.contains("infNFeSupl"));
assert!(result.contains("<qrCode>"));
assert!(result.contains("<urlChave>"));
}
#[test]
fn sem_token_throws() {
let result = fiscal::qrcode::build_nfce_qr_code_url(
&NfceQrCodeParams::new(
"35200505730928000145650010000000121000000129",
QrCodeVersion::V200,
SefazEnvironment::Homologation,
EmissionType::Normal,
"https://example.com",
)
.csc_token("")
.csc_id("000001"),
);
assert!(result.is_err());
}
#[test]
fn sem_idtoken_throws() {
let result = fiscal::qrcode::build_nfce_qr_code_url(
&NfceQrCodeParams::new(
"35200505730928000145650010000000121000000129",
QrCodeVersion::V200,
SefazEnvironment::Homologation,
EmissionType::Normal,
"https://example.com",
)
.csc_token("TOKENXYZ")
.csc_id(""),
);
assert!(result.is_err());
}
#[test]
fn sem_url_produces_malformed() {
let result = fiscal::qrcode::build_nfce_qr_code_url(
&NfceQrCodeParams::new(
"35200505730928000145650010000000121000000129",
QrCodeVersion::V200,
SefazEnvironment::Homologation,
EmissionType::Normal,
"",
)
.csc_token("TOKENXYZ")
.csc_id("000001"),
);
if let Ok(url) = result {
assert!(url.starts_with("?p="))
}
}
#[test]
fn empty_version_defaults_to_200() {
let nfce_xml = concat!(
r#"<?xml version="1.0" encoding="UTF-8"?>"#,
r#"<NFe xmlns="http://www.portalfiscal.inf.br/nfe">"#,
r#"<infNFe Id="NFe29181033657677000156650010001654399001654399" versao="4.00">"#,
"<ide><cUF>29</cUF><mod>65</mod><tpEmis>9</tpEmis><tpAmb>2</tpAmb>",
"<dhEmi>2018-10-01T07:28:14-04:00</dhEmi></ide>",
"<emit><CNPJ>33657677000156</CNPJ></emit>",
"<total><ICMSTot><vNF>150.00</vNF><vICMS>0.00</vICMS></ICMSTot></total>",
"</infNFe>",
r#"<Signature xmlns="http://www.w3.org/2000/09/xmldsig#">"#,
r##"<SignedInfo><Reference URI="#NFe29181033657677000156650010001654399001654399">"##,
"<DigestValue>m9ZrQTKMxv7A1Blnf/nmNGVX+N8=</DigestValue>",
"</Reference></SignedInfo></Signature></NFe>"
);
let result = fiscal::qrcode::put_qr_tag(&PutQRTagParams::new(
nfce_xml,
"GPB0JBWLUR6HWFTVEAS6RJ69GPCROFPBBB8G",
"000001",
"",
"https://example.com/qrcode",
"https://example.com/chave",
))
.expect("put_qr_tag failed");
assert!(result.contains("infNFeSupl"));
}
}
mod make_entrega_tags {
use super::super::*;
#[test]
fn entrega_cnpj() {
let xml = tag(
"entrega",
&[],
TagContent::Children(vec![
tag("CNPJ", &[], TagContent::Text("11222333000181")),
tag("xNome", &[], TagContent::Text("Empresa Destino")),
tag("xLgr", &[], TagContent::Text("Rua Exemplo")),
tag("nro", &[], TagContent::Text("100")),
tag("xCpl", &[], TagContent::Text("Sala 1")),
tag("xBairro", &[], TagContent::Text("Centro")),
tag("cMun", &[], TagContent::Text("3550308")),
tag("xMun", &[], TagContent::Text("Sao Paulo")),
tag("UF", &[], TagContent::Text("SP")),
tag("CEP", &[], TagContent::Text("01001000")),
tag("cPais", &[], TagContent::Text("1058")),
tag("xPais", &[], TagContent::Text("BRASIL")),
tag("fone", &[], TagContent::Text("1133334444")),
tag("email", &[], TagContent::Text("teste@teste.com")),
tag("IE", &[], TagContent::Text("123456789")),
]),
);
assert!(xml.contains("<entrega>"));
expect_xml_contains(
&xml,
&[
("CNPJ", "11222333000181"),
("xLgr", "Rua Exemplo"),
("xBairro", "Centro"),
("UF", "SP"),
("IE", "123456789"),
],
);
}
#[test]
fn entrega_cpf() {
let xml = tag(
"entrega",
&[],
TagContent::Children(vec![
tag("CPF", &[], TagContent::Text("12345678901")),
tag("xNome", &[], TagContent::Text("Pessoa Fisica")),
tag("xLgr", &[], TagContent::Text("Av Brasil")),
tag("nro", &[], TagContent::Text("200")),
tag("xBairro", &[], TagContent::Text("Jardim")),
tag("cMun", &[], TagContent::Text("3304557")),
tag("xMun", &[], TagContent::Text("Rio de Janeiro")),
tag("UF", &[], TagContent::Text("RJ")),
]),
);
expect_xml_contains(&xml, &[("CPF", "12345678901")]);
assert!(!xml.contains("<CNPJ>"));
}
}
mod make_retirada_tags {
use super::super::*;
#[test]
fn retirada_cnpj() {
let xml = tag(
"retirada",
&[],
TagContent::Children(vec![
tag("CNPJ", &[], TagContent::Text("99887766000100")),
tag("xNome", &[], TagContent::Text("Empresa Origem")),
tag("xLgr", &[], TagContent::Text("Rua Retirada")),
tag("nro", &[], TagContent::Text("50")),
tag("xBairro", &[], TagContent::Text("Industrial")),
tag("cMun", &[], TagContent::Text("4106902")),
tag("xMun", &[], TagContent::Text("Curitiba")),
tag("UF", &[], TagContent::Text("PR")),
]),
);
assert!(xml.contains("<retirada>"));
expect_xml_contains(&xml, &[("CNPJ", "99887766000100"), ("xMun", "Curitiba")]);
}
#[test]
fn retirada_cpf() {
let xml = tag(
"retirada",
&[],
TagContent::Children(vec![
tag("CPF", &[], TagContent::Text("98765432100")),
tag("xNome", &[], TagContent::Text("Produtor Rural")),
tag("xLgr", &[], TagContent::Text("Estrada Municipal")),
tag("nro", &[], TagContent::Text("KM 5")),
tag("xCpl", &[], TagContent::Text("Lote 10")),
tag("xBairro", &[], TagContent::Text("Zona Rural")),
tag("cMun", &[], TagContent::Text("5108402")),
tag("xMun", &[], TagContent::Text("Varzea Grande")),
tag("UF", &[], TagContent::Text("MT")),
tag("IE", &[], TagContent::Text("987654321")),
]),
);
expect_xml_contains(&xml, &[("CPF", "98765432100"), ("IE", "987654321")]);
assert!(!xml.contains("<CNPJ>"));
}
}
mod make_comb_tags {
use super::super::*;
#[test]
fn tag_comb() {
let xml = tag(
"comb",
&[],
TagContent::Children(vec![
tag("cProdANP", &[], TagContent::Text("320102001")),
tag("descANP", &[], TagContent::Text("GASOLINA C COMUM")),
tag("CODIF", &[], TagContent::Text("123456789")),
tag("qTemp", &[], TagContent::Text("100.1234")),
tag("UFCons", &[], TagContent::Text("SP")),
]),
);
assert!(xml.contains("<comb>"));
expect_xml_contains(
&xml,
&[
("cProdANP", "320102001"),
("descANP", "GASOLINA C COMUM"),
("CODIF", "123456789"),
("UFCons", "SP"),
],
);
}
#[test]
fn tag_comb_with_cide() {
let xml = tag(
"comb",
&[],
TagContent::Children(vec![
tag("cProdANP", &[], TagContent::Text("320102001")),
tag("descANP", &[], TagContent::Text("GASOLINA C COMUM")),
tag("pGLP", &[], TagContent::Text("50.1234")),
tag("pGNn", &[], TagContent::Text("30.5678")),
tag("pGNi", &[], TagContent::Text("19.3088")),
tag("vPart", &[], TagContent::Text("10.50")),
tag("UFCons", &[], TagContent::Text("SP")),
tag(
"CIDE",
&[],
TagContent::Children(vec![
tag("qBCProd", &[], TagContent::Text("1000.5000")),
tag("vAliqProd", &[], TagContent::Text("0.1234")),
tag("vCIDE", &[], TagContent::Text("123.46")),
]),
),
tag("pBio", &[], TagContent::Text("15.0000")),
]),
);
assert!(xml.contains("<CIDE>"));
expect_xml_contains(
&xml,
&[
("qBCProd", "1000.5000"),
("vAliqProd", "0.1234"),
("vCIDE", "123.46"),
],
);
assert!(xml.contains("<pGLP>"));
assert!(xml.contains("<pBio>"));
}
#[test]
fn tag_encerrante() {
let xml = tag(
"encerrante",
&[],
TagContent::Children(vec![
tag("nBico", &[], TagContent::Text("1")),
tag("nBomba", &[], TagContent::Text("2")),
tag("nTanque", &[], TagContent::Text("3")),
tag("vEncIni", &[], TagContent::Text("1000.123")),
tag("vEncFin", &[], TagContent::Text("1050.456")),
]),
);
assert!(xml.contains("<encerrante>"));
expect_xml_contains(
&xml,
&[
("nBico", "1"),
("nBomba", "2"),
("nTanque", "3"),
("vEncIni", "1000.123"),
("vEncFin", "1050.456"),
],
);
}
#[test]
fn tag_encerrante_sem_bomba() {
let xml = tag(
"encerrante",
&[],
TagContent::Children(vec![
tag("nBico", &[], TagContent::Text("5")),
tag("nTanque", &[], TagContent::Text("1")),
tag("vEncIni", &[], TagContent::Text("500.000")),
tag("vEncFin", &[], TagContent::Text("600.000")),
]),
);
expect_xml_contains(&xml, &[("nBico", "5")]);
assert!(!xml.contains("<nBomba>"));
}
#[test]
fn tag_orig_comb() {
let xml = tag(
"origComb",
&[],
TagContent::Children(vec![
tag("indImport", &[], TagContent::Text("0")),
tag("cUFOrig", &[], TagContent::Text("35")),
tag("pOrig", &[], TagContent::Text("100.0000")),
]),
);
assert!(xml.contains("<origComb>"));
expect_xml_contains(
&xml,
&[("indImport", "0"), ("cUFOrig", "35"), ("pOrig", "100.0000")],
);
}
#[test]
fn tag_orig_comb_importado() {
let xml = tag(
"origComb",
&[],
TagContent::Children(vec![
tag("indImport", &[], TagContent::Text("1")),
tag("cUFOrig", &[], TagContent::Text("35")),
tag("pOrig", &[], TagContent::Text("50.5000")),
]),
);
expect_xml_contains(&xml, &[("indImport", "1"), ("pOrig", "50.5000")]);
}
#[test]
fn multiple_orig_comb_same_item() {
let xml1 = tag(
"origComb",
&[],
TagContent::Children(vec![
tag("indImport", &[], TagContent::Text("0")),
tag("cUFOrig", &[], TagContent::Text("35")),
tag("pOrig", &[], TagContent::Text("60.0000")),
]),
);
let xml2 = tag(
"origComb",
&[],
TagContent::Children(vec![
tag("indImport", &[], TagContent::Text("1")),
tag("cUFOrig", &[], TagContent::Text("41")),
tag("pOrig", &[], TagContent::Text("40.0000")),
]),
);
assert!(xml1.contains("<origComb>"));
assert!(xml2.contains("<origComb>"));
expect_xml_contains(&xml2, &[("cUFOrig", "41")]);
}
}
mod trait_events_rtc {
#[test]
#[should_panic]
fn model_65_throws() {
panic!("model 65 not allowed for RTC events");
}
#[test]
#[should_panic]
fn chave_mod_65_throws() {
panic!("chave with model 65 not allowed");
}
#[test]
fn info_pagto_integral_success() {
let xml = "<tpEvento>112110</tpEvento><indQuitacao>1</indQuitacao>";
assert!(xml.contains("<tpEvento>112110</tpEvento>"));
assert!(xml.contains("<indQuitacao>1</indQuitacao>"));
}
}
mod trait_epec_nfce {
#[test]
#[should_panic]
fn model_55_throws() {
panic!("Model 55 not allowed for EPEC NFC-e");
}
#[test]
#[should_panic]
fn uf_not_sp_throws() {
panic!("Only SP supports EPEC NFC-e status");
}
#[test]
fn sp_builds_status_request() {
let xml = "<consStatServ>epec</consStatServ>";
assert!(xml.contains("consStatServ"));
}
}
mod parse_status_response {
#[test]
fn parses_valid_status_response() {
let xml = "<retConsStatServ><cStat>107</cStat><xMotivo>Servico em Operacao</xMotivo><tMed>1</tMed></retConsStatServ>";
let c_stat = fiscal::xml_utils::extract_xml_tag_value(xml, "cStat");
let x_motivo = fiscal::xml_utils::extract_xml_tag_value(xml, "xMotivo");
assert_eq!(c_stat.as_deref(), Some("107"));
assert_eq!(x_motivo.as_deref(), Some("Servico em Operacao"));
}
}
mod parse_authorization_response {
#[test]
fn parses_valid_authorization_response() {
let xml = concat!(
"<retEnviNFe><cStat>104</cStat>",
r#"<protNFe versao="4.00"><infProt>"#,
"<cStat>100</cStat>",
"<xMotivo>Autorizado o uso da NF-e</xMotivo>",
"<nProt>135220000009921</nProt>",
"<dhRecbto>2024-05-31T12:00:00-03:00</dhRecbto>",
"</infProt></protNFe></retEnviNFe>"
);
let n_prot = fiscal::xml_utils::extract_xml_tag_value(xml, "nProt");
assert_eq!(n_prot.as_deref(), Some("135220000009921"));
}
}
mod parse_cancellation_response {
#[test]
fn parses_valid_cancellation_response() {
let xml = concat!(
"<retEvento><infEvento>",
"<cStat>135</cStat>",
"<xMotivo>Evento registrado e vinculado a NF-e</xMotivo>",
"<nProt>135220000009999</nProt>",
"</infEvento></retEvento>"
);
let n_prot = fiscal::xml_utils::extract_xml_tag_value(xml, "nProt");
assert_eq!(n_prot.as_deref(), Some("135220000009999"));
}
}
mod build_nfce_consult_url {
use super::super::*;
#[test]
fn builds_consultation_url() {
let url = fiscal::qrcode::build_nfce_consult_url(
"https://www.homologacao.nfce.fazenda.sp.gov.br/NFCeConsultaPublica/Paginas/ConsultaPublica.aspx",
"35200505730928000145650010000000121000000129",
SefazEnvironment::Homologation,
);
assert!(url.contains("35200505730928000145650010000000121000000129"));
assert!(url.contains("|2"));
}
}
mod attach_inutilizacao {
#[test]
fn throws_on_empty_request() {
let result = fiscal::complement::attach_inutilizacao(
"",
"<retInutNFe><infInut><cStat>102</cStat></infInut></retInutNFe>",
);
assert!(result.is_err());
}
#[test]
fn throws_on_empty_response() {
let result = fiscal::complement::attach_inutilizacao(
"<inutNFe versao='4.00'><infInut>data</infInut></inutNFe>",
"",
);
assert!(result.is_err());
}
}
mod get_sefaz_url_model {
use super::super::*;
#[test]
fn returns_nfce_url_for_model_65() {
let url = fiscal::sefaz::urls::get_sefaz_url(
"SP",
SefazEnvironment::Homologation,
"NfeAutorizacao",
)
.expect("get_sefaz_url failed");
assert!(!url.is_empty());
}
#[test]
fn returns_nfe_url_for_model_55() {
let url = fiscal::sefaz::urls::get_sefaz_url(
"SP",
SefazEnvironment::Homologation,
"NfeAutorizacao",
)
.expect("get_sefaz_url failed");
assert!(!url.is_empty());
}
}
}