mod common;
use fiscal::types::*;
use fiscal::xml_utils::{TagContent, tag};
use common::{FIXTURES_PATH, expect_xml_tag_values as expect_xml_contains};
mod config_test {
use fiscal::FiscalError;
use fiscal::config::validate_config;
#[test]
fn validate_config_json_returns_object() {
let json = r#"{"tpAmb":2,"razaosocial":"SUA RAZAO SOCIAL LTDA","siglaUF":"SP","cnpj":"93623057000128","schemes":"PL_010_V1.30","versao":"4.00"}"#;
let config = validate_config(json).expect("validate failed");
assert_eq!(config.tp_amb, 2);
assert_eq!(config.cnpj, "93623057000128");
}
#[test]
fn config_without_optional_fields_is_valid() {
let json = r#"{"tpAmb":2,"razaosocial":"SUA RAZAO SOCIAL LTDA","siglaUF":"SP","cnpj":"99999999999999","schemes":"PL_009_V4","versao":"4.00"}"#;
let config = validate_config(json);
assert!(config.is_ok());
}
#[test]
fn config_with_array_instead_of_json_string_fails() {
let result = validate_config("[1,2,3]");
assert!(result.is_err());
}
#[test]
fn empty_string_fails_parse() {
let result = validate_config("");
assert!(result.is_err());
}
#[test]
fn missing_tp_amb_fails() {
let json = r#"{"razaosocial":"SUA RAZAO SOCIAL LTDA","siglaUF":"SP","cnpj":"99999999999999","schemes":"PL_009_V4","versao":"4.00"}"#;
let err = validate_config(json).unwrap_err();
match &err {
FiscalError::ConfigValidation(msg) => assert!(msg.contains("[tpAmb]")),
_ => panic!("expected ConfigValidation, got: {err:?}"),
}
}
#[test]
fn missing_razaosocial_fails() {
let json = r#"{"tpAmb":2,"siglaUF":"SP","cnpj":"99999999999999","schemes":"PL_009_V4","versao":"4.00"}"#;
let err = validate_config(json).unwrap_err();
match &err {
FiscalError::ConfigValidation(msg) => assert!(msg.contains("[razaosocial]")),
_ => panic!("expected ConfigValidation, got: {err:?}"),
}
}
#[test]
fn missing_sigla_uf_fails() {
let json = r#"{"tpAmb":2,"razaosocial":"SUA RAZAO SOCIAL LTDA","cnpj":"99999999999999","schemes":"PL_009_V4","versao":"4.00"}"#;
let err = validate_config(json).unwrap_err();
match &err {
FiscalError::ConfigValidation(msg) => assert!(msg.contains("[siglaUF]")),
_ => panic!("expected ConfigValidation, got: {err:?}"),
}
}
#[test]
fn missing_cnpj_fails() {
let json = r#"{"tpAmb":2,"razaosocial":"SUA RAZAO SOCIAL LTDA","siglaUF":"SP","schemes":"PL_008_V4","versao":"4.00"}"#;
let err = validate_config(json).unwrap_err();
match &err {
FiscalError::ConfigValidation(msg) => assert!(msg.contains("[cnpj]")),
_ => panic!("expected ConfigValidation, got: {err:?}"),
}
}
#[test]
fn missing_schemes_fails() {
let json = r#"{"tpAmb":2,"razaosocial":"SUA RAZAO SOCIAL LTDA","siglaUF":"SP","cnpj":"99999999999999","versao":"4.00"}"#;
let err = validate_config(json).unwrap_err();
match &err {
FiscalError::ConfigValidation(msg) => assert!(msg.contains("[schemes]")),
_ => panic!("expected ConfigValidation, got: {err:?}"),
}
}
#[test]
fn missing_versao_fails() {
let json = r#"{"tpAmb":2,"razaosocial":"SUA RAZAO SOCIAL LTDA","siglaUF":"SP","cnpj":"99999999999999","schemes":"PL_009_V4"}"#;
let err = validate_config(json).unwrap_err();
match &err {
FiscalError::ConfigValidation(msg) => assert!(msg.contains("[versao]")),
_ => panic!("expected ConfigValidation, got: {err:?}"),
}
}
#[test]
fn config_with_cpf_is_valid() {
let json = r#"{"tpAmb":2,"razaosocial":"SUA RAZAO SOCIAL LTDA","siglaUF":"SP","cnpj":"99999999999","schemes":"PL_009_V4","versao":"4.00"}"#;
let config = validate_config(json).expect("validate failed");
assert_eq!(config.cnpj, "99999999999");
}
}
mod standardize_test {
use super::*;
fn nfe_xml_path() -> String {
format!("{}xml/2017nfe_antiga_v310.xml", FIXTURES_PATH)
}
fn cte_xml_path() -> String {
format!("{}xml/cte.xml", FIXTURES_PATH)
}
#[test]
fn detect_nfe_xml_type_returns_nfe() {
let xml = std::fs::read_to_string(nfe_xml_path()).expect("Failed to read NFe XML");
let result = fiscal::standardize::identify_xml_type(&xml).expect("identify failed");
assert_eq!(result, "NFe");
}
#[test]
fn non_xml_string_throws() {
let result = fiscal::standardize::identify_xml_type("jslsj ks slk lk");
assert!(result.is_err());
}
#[test]
fn whitespace_only_string_throws() {
let result = fiscal::standardize::identify_xml_type(" ");
assert!(result.is_err());
}
#[test]
fn empty_string_throws() {
let result = fiscal::standardize::identify_xml_type("");
assert!(result.is_err());
}
#[test]
fn numeric_input_throws() {
let result = fiscal::standardize::identify_xml_type("100");
assert!(result.is_err());
}
#[test]
fn null_input_throws() {
let result = fiscal::standardize::identify_xml_type("");
assert!(result.is_err());
}
#[test]
fn convert_nfe_xml_to_array_object() {
let xml = std::fs::read_to_string(nfe_xml_path()).expect("Failed to read NFe XML");
let json = fiscal::standardize::xml_to_json(&xml).expect("xml_to_json failed");
let parsed: serde_json::Value = serde_json::from_str(&json).expect("JSON parse failed");
assert!(parsed.is_object());
assert!(
parsed.get("infNFe").is_some(),
"infNFe at top level after NFe unwrap"
);
}
#[test]
fn convert_nfe_xml_to_std_object() {
let xml = std::fs::read_to_string(nfe_xml_path()).expect("Failed to read NFe XML");
let json = fiscal::standardize::xml_to_json(&xml).expect("xml_to_json failed");
let parsed: serde_json::Value = serde_json::from_str(&json).expect("JSON parse failed");
assert!(
parsed.get("infNFe").is_some(),
"infNFe at top level after NFe unwrap"
);
}
#[test]
fn cte_xml_throws() {
let xml = std::fs::read_to_string(cte_xml_path()).expect("Failed to read CTe XML");
let result = fiscal::standardize::identify_xml_type(&xml);
assert!(result.is_err());
}
#[test]
fn convert_nfe_xml_to_json_string() {
let xml = std::fs::read_to_string(nfe_xml_path()).expect("Failed to read NFe XML");
let json = fiscal::standardize::xml_to_json(&xml).expect("xml_to_json failed");
assert!(!json.is_empty());
let parsed: serde_json::Value = serde_json::from_str(&json).expect("JSON parse failed");
assert!(
parsed.get("infNFe").is_some(),
"infNFe at top level after NFe unwrap"
);
}
}
mod webservices_test {
use super::*;
#[test]
fn get_sefaz_url_exists_and_is_callable() {
let _result = fiscal::sefaz::urls::get_sefaz_url(
"RS",
SefazEnvironment::Homologation,
"NfeStatusServico",
);
}
#[test]
fn rs_homologation_model_55_returns_url() {
let url = fiscal::sefaz::urls::get_sefaz_url(
"RS",
SefazEnvironment::Homologation,
"NfeStatusServico",
)
.expect("get_sefaz_url failed");
assert!(url.contains("https://"));
assert!(url.contains("sefazrs") || url.contains("rs.gov.br"));
}
#[test]
fn invalid_uf_xy_throws() {
let result = fiscal::sefaz::urls::get_sefaz_url(
"XY",
SefazEnvironment::Homologation,
"NfeStatusServico",
);
assert!(result.is_err());
}
}
mod gtin_test {
#[test]
fn empty_string_and_sem_gtin_and_valid_gtin_are_valid() {
assert!(fiscal::gtin::is_valid_gtin("").unwrap());
assert!(fiscal::gtin::is_valid_gtin("SEM GTIN").unwrap());
assert!(fiscal::gtin::is_valid_gtin("7898357410015").unwrap());
}
#[test]
fn bad_check_digit_throws() {
let result = fiscal::gtin::is_valid_gtin("7898357410010");
assert!(result.is_err());
}
#[test]
fn non_numeric_throws() {
let result = fiscal::gtin::is_valid_gtin("abc");
assert!(result.is_err());
}
}
mod valid_txt_test {
use super::*;
#[test]
fn invalid_txt_returns_validation_errors() {
let bytes = std::fs::read(format!("{FIXTURES_PATH}txt/nfe_errado.txt"))
.expect("Failed to read nfe_errado.txt");
let txt = String::from_utf8_lossy(&bytes);
let result = fiscal::convert::validate_txt(&txt, "local");
if let Ok(valid) = result {
assert!(!valid, "Expected validation to fail")
}
}
#[test]
fn valid_sebrae_format_txt_passes_validation() {
let txt = std::fs::read_to_string(format!("{FIXTURES_PATH}txt/nota_4.00_sebrae.txt"))
.expect("Failed to read sebrae txt");
let result = fiscal::convert::validate_txt(&txt, "sebrae");
let _ = result;
}
#[test]
fn valid_local_format_txt_passes_validation() {
let txt = std::fs::read_to_string(format!("{FIXTURES_PATH}txt/nfe_4.00_local_01.txt"))
.expect("Failed to read local txt");
let result = fiscal::convert::validate_txt(&txt, "local");
let _ = result;
}
}
mod certificate_test {
const CNPJ_PFX_PATH: &str = concat!(
env!("CARGO_MANIFEST_DIR"),
"/tests/fixtures/certs/novo_cert_cnpj_06157250000116_senha_minhasenha.pfx"
);
const CPF_PFX_PATH: &str = concat!(
env!("CARGO_MANIFEST_DIR"),
"/tests/fixtures/certs/novo_cert_cpf_90483926086_minhasenha.pfx"
);
const PASSWORD: &str = "minhasenha";
#[test]
fn pj_certificate_extracts_cnpj_and_validity_date() {
use chrono::Datelike;
let pfx_bytes = std::fs::read(CNPJ_PFX_PATH).expect("Failed to read PJ PFX");
let cert_data = fiscal::certificate::load_certificate(&pfx_bytes, PASSWORD)
.expect("load_certificate failed");
assert!(
cert_data
.private_key
.contains("-----BEGIN PRIVATE KEY-----")
);
assert!(
cert_data
.certificate
.contains("-----BEGIN CERTIFICATE-----")
);
let info = fiscal::certificate::get_certificate_info(&pfx_bytes, PASSWORD)
.expect("get_certificate_info failed");
assert!(info.common_name.contains("06.157.250/0001-16"));
assert_eq!(info.valid_until.year(), 2034);
assert_eq!(info.valid_until.month(), 6);
assert_eq!(info.valid_until.day(), 5);
}
#[test]
fn pf_certificate_extracts_cpf_and_validity_date() {
use chrono::Datelike;
let pfx_bytes = std::fs::read(CPF_PFX_PATH).expect("Failed to read PF PFX");
let cert_data = fiscal::certificate::load_certificate(&pfx_bytes, PASSWORD)
.expect("load_certificate failed");
assert!(
cert_data
.private_key
.contains("-----BEGIN PRIVATE KEY-----")
);
assert!(
cert_data
.certificate
.contains("-----BEGIN CERTIFICATE-----")
);
let info = fiscal::certificate::get_certificate_info(&pfx_bytes, PASSWORD)
.expect("get_certificate_info failed");
assert!(info.common_name.contains("904.839.260-86"));
assert_eq!(info.valid_until.year(), 2034);
assert_eq!(info.valid_until.month(), 6);
assert_eq!(info.valid_until.day(), 3);
}
}
mod convert_test {
use super::*;
fn convert_local_txt() -> String {
let txt = std::fs::read_to_string(format!("{FIXTURES_PATH}txt/nfe_4.00_local_01.txt"))
.expect("Failed to read local txt");
fiscal::convert::txt_to_xml(&txt, "local_v12").expect("txt_to_xml failed")
}
#[test]
fn convert_local_txt_to_xml_produces_nfe() {
let txt = std::fs::read_to_string(format!("{FIXTURES_PATH}txt/nfe_4.00_local_01.txt"))
.expect("Failed to read local txt");
let xml = fiscal::convert::txt_to_xml(&txt, "local_v12").expect("txt_to_xml failed");
assert!(xml.contains("<NFe"));
assert!(xml.contains("<infNFe"));
}
#[test]
fn convert_txt_with_invalid_key_throws() {
let txt = std::fs::read_to_string(format!("{FIXTURES_PATH}txt/nfe_4.00_local_error.txt"))
.expect("Failed to read error txt");
let result = fiscal::convert::txt_to_xml(&txt, "local_v12");
assert!(result.is_err());
}
#[test]
fn convert_dump_returns_correct_id() {
let txt = std::fs::read_to_string(format!("{FIXTURES_PATH}txt/nfe_4.00_local_01.txt"))
.expect("Failed to read local txt");
let xml = fiscal::convert::txt_to_xml(&txt, "local_v12").expect("txt_to_xml failed");
assert!(xml.contains("NFe35180825028332000105550010000005021000005010"));
}
#[test]
fn converted_xml_has_correct_ide_fields() {
let xml = convert_local_txt();
expect_xml_contains(
&xml,
&[
("cUF", "35"),
("cNF", "00000501"),
("natOp", "VENDA MERC.SUB.TRIBUTARIA"),
("mod", "55"),
("serie", "1"),
("nNF", "502"),
("dhEmi", "2018-08-13T17:28:10-03:00"),
("dhSaiEnt", "2018-08-14T09:00:00-03:00"),
("tpNF", "1"),
("idDest", "1"),
("cMunFG", "3550308"),
("tpImp", "1"),
("tpEmis", "1"),
("cDV", "8"),
("tpAmb", "1"),
("finNFe", "1"),
("indFinal", "0"),
("indPres", "3"),
("indIntermed", "0"),
("procEmi", "0"),
("verProc", "3.2.1.1"),
],
);
}
#[test]
fn converted_xml_has_correct_emit_fields() {
let xml = convert_local_txt();
expect_xml_contains(
&xml,
&[
("CNPJ", "25028332000105"),
("xNome", "GSMMY COMERCIO DE CHOCOLATES LTDA"),
("IE", "140950881119"),
("CRT", "3"),
],
);
assert!(xml.contains("<enderEmit>"));
expect_xml_contains(
&xml,
&[
("xLgr", "RUA CAETEZAL"),
("nro", "296"),
("xBairro", "AGUA FRIA"),
("xMun", "SAO PAULO"),
("UF", "SP"),
("CEP", "02334130"),
],
);
}
#[test]
fn converted_xml_has_correct_dest_fields() {
let xml = convert_local_txt();
assert!(xml.contains("<dest>"));
assert!(xml.contains("17812455000295"));
assert!(xml.contains("SILVANA MARCONI - VL LEOPOLDINA"));
assert!(xml.contains("<indIEDest>1</indIEDest>"));
assert!(xml.contains("142304338112"));
assert!(xml.contains("vilaleopoldina@munik.com.br"));
assert!(xml.contains("<enderDest>"));
assert!(xml.contains("R SCHILLING"));
assert!(xml.contains("<nro>491</nro>"));
assert!(xml.contains("VILA LEOPOLDINA"));
assert!(xml.contains("05302001"));
}
#[test]
fn converted_xml_has_4_det_items() {
let xml = convert_local_txt();
let det_count = xml.matches("<det ").count();
assert_eq!(det_count, 4);
assert!(xml.contains("<cProd>11352</cProd>"));
assert!(xml.contains("<cEAN>7897112913525</cEAN>"));
assert!(xml.contains("CX DE BOMBOM SORTIDO 105G - 11352"));
assert!(xml.contains("<NCM>18069000</NCM>"));
assert!(xml.contains("<CEST>1700700</CEST>"));
assert!(xml.contains("<CFOP>5401</CFOP>"));
assert!(xml.contains("<uCom>CX</uCom>"));
assert!(xml.contains("<vProd>25.30</vProd>"));
assert!(xml.contains("<indTot>1</indTot>"));
assert!(xml.contains("<nItemPed>0</nItemPed>"));
assert!(xml.contains("<gCred>"));
assert!(xml.contains("<cCredPresumido>1</cCredPresumido>"));
assert!(xml.contains("<pCredPresumido>10.0000</pCredPresumido>"));
assert!(xml.contains("<vCredPresumido>100.00</vCredPresumido>"));
assert!(xml.contains("<vTotTrib>0.00</vTotTrib>"));
assert!(xml.contains("<ICMS10>"));
assert!(xml.contains("<orig>0</orig>"));
assert!(xml.contains("<CST>10</CST>"));
assert!(xml.contains("<IPI>"));
assert!(xml.contains("<cEnq>999</cEnq>"));
assert!(xml.contains("<IPITrib>"));
assert!(xml.contains("<PISAliq>"));
assert!(xml.contains("<pPIS>"));
assert!(xml.contains("<COFINSAliq>"));
assert!(xml.contains("<pCOFINS>"));
assert!(xml.contains("<cProd>14169</cProd>"));
assert!(xml.contains("<cProd>355</cProd>"));
assert!(xml.contains("<cProd>45</cProd>"));
}
#[test]
fn converted_xml_has_correct_icms_tot_fields() {
let xml = convert_local_txt();
assert!(xml.contains("<ICMSTot>"));
expect_xml_contains(
&xml,
&[
("vBC", "55.84"),
("vICMS", "10.04"),
("vICMSDeson", "0.00"),
("vFCP", "0.00"),
("vBCST", "94.87"),
("vST", "12.39"),
("vFCPST", "0.00"),
("vFCPSTRet", "0.00"),
("vProd", "103.88"),
("vFrete", "0.00"),
("vSeg", "0.00"),
("vDesc", "0.00"),
("vII", "0.00"),
("vIPI", "0.12"),
("vIPIDevol", "0.00"),
("vPIS", "0.67"),
("vCOFINS", "3.12"),
("vOutro", "0.00"),
("vNF", "116.39"),
],
);
}
#[test]
fn converted_xml_has_correct_transp_fields() {
let xml = convert_local_txt();
expect_xml_contains(&xml, &[("modFrete", "3")]);
assert!(xml.contains("<transporta>"));
assert!(xml.contains("47269568000257"));
assert!(xml.contains("CARRO PROPRIO -MUNIK"));
assert!(xml.contains("111220540115"));
assert!(xml.contains("R CAITEZAL, 316"));
assert!(xml.contains("<vol>"));
assert!(xml.contains("<qVol>1</qVol>"));
assert!(xml.contains("<esp>VOLUME</esp>"));
assert!(xml.contains("<marca>MUNIK</marca>"));
assert!(xml.contains("<pesoL>4.230</pesoL>"));
assert!(xml.contains("<pesoB>4.230</pesoB>"));
}
#[test]
fn converted_xml_has_correct_cobr_fields() {
let xml = convert_local_txt();
assert!(xml.contains("<cobr>"));
expect_xml_contains(
&xml,
&[("nFat", "502"), ("vOrig", "116.39"), ("vLiq", "116.39")],
);
assert!(xml.contains("<dup>"));
expect_xml_contains(
&xml,
&[("nDup", "001"), ("dVenc", "2018-08-13"), ("vDup", "116.39")],
);
}
#[test]
fn converted_xml_has_correct_pag_fields() {
let xml = convert_local_txt();
assert!(xml.contains("<pag>"));
assert!(xml.contains("<detPag>"));
expect_xml_contains(&xml, &[("indPag", "0"), ("tPag", "01"), ("vPag", "116.39")]);
}
#[test]
fn converted_xml_has_correct_inf_adic() {
let xml = convert_local_txt();
assert!(xml.contains("<infAdic>"));
assert!(xml.contains("BASE DO ICMS REDUZIDA EM 61,11 CF RICMS Pedido 000068"));
}
#[test]
fn txt_to_xml_all_returns_single_invoice() {
let txt = std::fs::read_to_string(format!("{FIXTURES_PATH}txt/nfe_4.00_local_01.txt"))
.expect("Failed to read local txt");
let xmls =
fiscal::convert::txt_to_xml_all(&txt, "local_v12").expect("txt_to_xml_all failed");
assert_eq!(xmls.len(), 1);
assert!(xmls[0].contains("<NFe"));
assert!(xmls[0].contains("NFe35180825028332000105550010000005021000005010"));
}
#[test]
fn txt_to_xml_all_returns_all_invoices_from_multi_note_txt() {
let txt =
std::fs::read_to_string(format!("{FIXTURES_PATH}txt/nfe_4.00_local_02_multi.txt"))
.expect("Failed to read multi-note txt");
let xmls =
fiscal::convert::txt_to_xml_all(&txt, "local_v12").expect("txt_to_xml_all failed");
assert_eq!(xmls.len(), 2, "Deve retornar 2 notas fiscais");
assert!(xmls[0].contains("<NFe"));
assert!(xmls[0].contains("NFe35180825028332000105550010000005021000005010"));
assert!(xmls[0].contains("<nNF>502</nNF>"));
assert!(xmls[0].contains("PRIMEIRA NOTA DO LOTE"));
assert!(xmls[0].contains("<cProd>11352</cProd>"));
assert!(xmls[1].contains("<NFe"));
assert!(xmls[1].contains("NFe35180825028332000105550010000005031000005020"));
assert!(xmls[1].contains("<nNF>503</nNF>"));
assert!(xmls[1].contains("SEGUNDA NOTA DO LOTE"));
assert!(xmls[1].contains("<cProd>14169</cProd>"));
}
#[test]
fn txt_to_xml_all_each_xml_is_independent() {
let txt =
std::fs::read_to_string(format!("{FIXTURES_PATH}txt/nfe_4.00_local_02_multi.txt"))
.expect("Failed to read multi-note txt");
let xmls =
fiscal::convert::txt_to_xml_all(&txt, "local_v12").expect("txt_to_xml_all failed");
assert!(
!xmls[0].contains("<nNF>503</nNF>"),
"Primeira nota não deve conter nNF da segunda"
);
assert!(
!xmls[0].contains("SEGUNDA NOTA DO LOTE"),
"Primeira nota não deve conter infAdFisco da segunda"
);
assert!(
!xmls[1].contains("<nNF>502</nNF>"),
"Segunda nota não deve conter nNF da primeira"
);
assert!(
!xmls[1].contains("PRIMEIRA NOTA DO LOTE"),
"Segunda nota não deve conter infAdFisco da primeira"
);
}
#[test]
fn txt_to_xml_returns_first_of_multi_note_txt() {
let txt =
std::fs::read_to_string(format!("{FIXTURES_PATH}txt/nfe_4.00_local_02_multi.txt"))
.expect("Failed to read multi-note txt");
let xml = fiscal::convert::txt_to_xml(&txt, "local_v12").expect("txt_to_xml failed");
assert!(xml.contains("<nNF>502</nNF>"));
assert!(xml.contains("PRIMEIRA NOTA DO LOTE"));
}
}
mod tipos_basicos_test {
#[test]
fn tstring_xsd_has_correct_pattern() {
let xsd_path = concat!(
env!("CARGO_MANIFEST_DIR"),
"/tests/fixtures/schemes/tiposBasico_v4.00.xsd"
);
let xsd = std::fs::read_to_string(xsd_path).expect("Failed to read XSD");
assert!(
xsd.contains("TString"),
"XSD should contain TString simpleType"
);
assert!(
xsd.contains("[!-\u{00FF}]"),
"XSD TString pattern should contain [!-\\xFF]"
);
}
}
mod make_dev_test {
use super::*;
#[test]
fn tag_inf_nfe_with_id_and_versao() {
let id = "35170358716523000119550010000000301000000300";
let xml = tag(
"infNFe",
&[("Id", &format!("NFe{id}")), ("versao", "4.00")],
TagContent::Text(""),
);
assert!(xml.contains(&format!(r#"Id="NFe{id}""#)));
assert!(xml.contains(r#"versao="4.00""#));
}
#[test]
fn tag_inf_nfe_with_pk_n_item() {
let id = "35170358716523000119550010000000301000000300";
let xml = tag(
"infNFe",
&[
("Id", &format!("NFe{id}")),
("versao", "4.00"),
("pk_nItem", "1"),
],
TagContent::Text(""),
);
assert!(xml.contains(&format!(r#"Id="NFe{id}""#)));
assert!(xml.contains(r#"versao="4.00""#));
assert!(xml.contains(r#"pk_nItem="1""#));
}
#[test]
fn tag_inf_nfe_without_access_key() {
let xml = tag(
"infNFe",
&[("Id", "NFe"), ("versao", "4.00")],
TagContent::Text(""),
);
assert!(xml.contains(r#"Id="NFe""#));
assert!(xml.contains(r#"versao="4.00""#));
}
#[test]
fn tag_ide_model_55_with_all_values() {
let xml = tag(
"ide",
&[],
TagContent::Children(vec![
tag("cUF", &[], TagContent::Text("50")),
tag("cNF", &[], TagContent::Text("80070008")),
tag("natOp", &[], TagContent::Text("VENDA")),
tag("mod", &[], TagContent::Text("55")),
tag("serie", &[], TagContent::Text("1")),
tag("nNF", &[], TagContent::Text("1")),
tag("dhEmi", &[], TagContent::Text("2018-06-23T17:45:49-03:00")),
tag(
"dhSaiEnt",
&[],
TagContent::Text("2018-06-23T17:45:49-03:00"),
),
tag("tpNF", &[], TagContent::Text("1")),
tag("idDest", &[], TagContent::Text("1")),
tag("cMunFG", &[], TagContent::Text("5002704")),
tag("tpImp", &[], TagContent::Text("1")),
tag("tpEmis", &[], TagContent::Text("1")),
tag("cDV", &[], TagContent::Text("2")),
tag("tpAmb", &[], TagContent::Text("2")),
tag("finNFe", &[], TagContent::Text("1")),
tag("indFinal", &[], TagContent::Text("0")),
tag("indPres", &[], TagContent::Text("1")),
tag("procEmi", &[], TagContent::Text("0")),
tag("verProc", &[], TagContent::Text("5.0")),
]),
);
expect_xml_contains(
&xml,
&[
("cUF", "50"),
("cNF", "80070008"),
("natOp", "VENDA"),
("mod", "55"),
("serie", "1"),
("nNF", "1"),
("dhEmi", "2018-06-23T17:45:49-03:00"),
("dhSaiEnt", "2018-06-23T17:45:49-03:00"),
("tpNF", "1"),
("idDest", "1"),
("cMunFG", "5002704"),
("tpImp", "1"),
("tpEmis", "1"),
("cDV", "2"),
("tpAmb", "2"),
("finNFe", "1"),
("indFinal", "0"),
("indPres", "1"),
("procEmi", "0"),
("verProc", "5.0"),
],
);
assert!(!xml.contains("<indPag>"));
assert!(!xml.contains("<dhCont>"));
assert!(!xml.contains("<xJust>"));
}
#[test]
fn tag_ide_empty_required_fields() {
let xml = tag(
"ide",
&[],
TagContent::Children(vec![
tag("cUF", &[], TagContent::Text("")),
tag("cNF", &[], TagContent::Text("78888888")),
tag("natOp", &[], TagContent::Text("")),
tag("mod", &[], TagContent::Text("")),
tag("serie", &[], TagContent::Text("")),
tag("nNF", &[], TagContent::Text("")),
tag("dhEmi", &[], TagContent::Text("")),
tag("tpNF", &[], TagContent::Text("")),
tag("idDest", &[], TagContent::Text("")),
tag("cMunFG", &[], TagContent::Text("")),
tag("tpImp", &[], TagContent::Text("1")),
tag("tpEmis", &[], TagContent::Text("1")),
tag("cDV", &[], TagContent::Text("0")),
tag("tpAmb", &[], TagContent::Text("")),
tag("finNFe", &[], TagContent::Text("")),
tag("indFinal", &[], TagContent::Text("")),
tag("indPres", &[], TagContent::Text("")),
tag("procEmi", &[], TagContent::Text("")),
tag("verProc", &[], TagContent::Text("")),
]),
);
assert!(xml.contains("<cUF></cUF>"));
assert!(xml.contains("<cNF>78888888</cNF>"));
assert!(xml.contains("<natOp></natOp>"));
assert!(xml.contains("<mod></mod>"));
assert!(xml.contains("<serie></serie>"));
assert!(xml.contains("<nNF></nNF>"));
assert!(xml.contains("<dhEmi></dhEmi>"));
assert!(xml.contains("<tpNF></tpNF>"));
assert!(xml.contains("<idDest></idDest>"));
assert!(xml.contains("<cMunFG></cMunFG>"));
assert!(xml.contains("<cDV>0</cDV>"));
assert!(xml.contains("<tpAmb></tpAmb>"));
assert!(xml.contains("<finNFe></finNFe>"));
assert!(xml.contains("<indFinal></indFinal>"));
assert!(xml.contains("<indPres></indPres>"));
assert!(xml.contains("<procEmi></procEmi>"));
assert!(xml.contains("<verProc></verProc>"));
}
#[test]
fn tag_ide_contingency_fields() {
let xml = tag(
"ide",
&[],
TagContent::Children(vec![
tag("dhCont", &[], TagContent::Text("2018-06-26T17:45:49-03:00")),
tag("xJust", &[], TagContent::Text("SEFAZ INDISPONIVEL")),
]),
);
expect_xml_contains(
&xml,
&[
("dhCont", "2018-06-26T17:45:49-03:00"),
("xJust", "SEFAZ INDISPONIVEL"),
],
);
}
#[test]
fn tag_ide_model_65_with_nfce_specifics() {
let xml = tag(
"ide",
&[],
TagContent::Children(vec![
tag("cUF", &[], TagContent::Text("50")),
tag("cNF", &[], TagContent::Text("80070008")),
tag("natOp", &[], TagContent::Text("VENDA")),
tag("mod", &[], TagContent::Text("65")),
tag("serie", &[], TagContent::Text("1")),
tag("nNF", &[], TagContent::Text("1")),
tag("dhEmi", &[], TagContent::Text("2018-06-23T17:45:49-03:00")),
tag("tpNF", &[], TagContent::Text("1")),
tag("idDest", &[], TagContent::Text("1")),
tag("cMunFG", &[], TagContent::Text("5002704")),
tag("cMunFGIBS", &[], TagContent::Text("5002704")),
tag("tpImp", &[], TagContent::Text("4")),
tag("tpEmis", &[], TagContent::Text("1")),
tag("tpNFDebito", &[], TagContent::Text("1")),
tag("cDV", &[], TagContent::Text("2")),
tag("tpAmb", &[], TagContent::Text("2")),
tag("finNFe", &[], TagContent::Text("1")),
tag("indFinal", &[], TagContent::Text("0")),
tag("indPres", &[], TagContent::Text("5")),
tag("indIntermed", &[], TagContent::Text("1")),
tag("procEmi", &[], TagContent::Text("0")),
tag("verProc", &[], TagContent::Text("5.0")),
]),
);
expect_xml_contains(
&xml,
&[
("cUF", "50"),
("mod", "65"),
("cMunFGIBS", "5002704"),
("tpImp", "4"),
("tpNFDebito", "1"),
("indPres", "5"),
("indIntermed", "1"),
],
);
assert!(!xml.contains("<indPag>"));
assert!(!xml.contains("<dhSaiEnt>"));
assert!(!xml.contains("<dhCont>"));
assert!(!xml.contains("<xJust>"));
assert!(!xml.contains("<tpNFCredito>"));
}
}
mod complements_test {
#[test]
fn attach_event_protocol_produces_proc_evento() {
let request_xml = r#"<?xml version="1.0" encoding="UTF-8"?>
<envEvento versao="1.00" xmlns="http://www.portalfiscal.inf.br/nfe">
<idLote>1</idLote>
<evento versao="1.00">
<infEvento Id="ID1101113518082502833200010555001000000502101">
<cOrgao>35</cOrgao>
<tpAmb>2</tpAmb>
<CNPJ>25028332000105</CNPJ>
<chNFe>35180825028332000105550010000005021000005010</chNFe>
<dhEvento>2018-08-15T10:00:00-03:00</dhEvento>
<tpEvento>110111</tpEvento>
<nSeqEvento>1</nSeqEvento>
<verEvento>1.00</verEvento>
<detEvento versao="1.00">
<descEvento>Cancelamento</descEvento>
<nProt>135180000000001</nProt>
<xJust>Cancelamento por erro de digitacao</xJust>
</detEvento>
</infEvento>
</evento>
</envEvento>"#;
let response_xml = r#"<?xml version="1.0" encoding="UTF-8"?>
<retEnvEvento versao="1.00" xmlns="http://www.portalfiscal.inf.br/nfe">
<idLote>1</idLote>
<tpAmb>2</tpAmb>
<cStat>128</cStat>
<xMotivo>Lote de Evento Processado</xMotivo>
<retEvento versao="1.00">
<infEvento>
<tpAmb>2</tpAmb>
<cStat>135</cStat>
<xMotivo>Evento registrado e vinculado a NF-e</xMotivo>
<chNFe>35180825028332000105550010000005021000005010</chNFe>
<tpEvento>110111</tpEvento>
<nSeqEvento>1</nSeqEvento>
<nProt>135180000000002</nProt>
</infEvento>
</retEvento>
</retEnvEvento>"#;
let result = fiscal::complement::attach_event_protocol(request_xml, response_xml)
.expect("attach_event_protocol failed");
assert!(result.contains("procEventoNFe"));
assert!(result.contains("<evento"));
assert!(result.contains("<retEvento"));
}
#[test]
fn wrong_document_type_throws() {
let wrong_xml = r#"<?xml version="1.0"?><cte><infCTe></infCTe></cte>"#;
let result = fiscal::complement::attach_protocol(wrong_xml, "<resp/>");
assert!(result.is_err());
}
#[test]
fn non_xml_input_throws() {
let result = fiscal::complement::attach_protocol("this is not xml", "<resp/>");
assert!(result.is_err());
}
#[test]
fn wrong_root_node_throws() {
let wrong_node_xml = r#"<?xml version="1.0"?><wrongRoot><data/></wrongRoot>"#;
let result = fiscal::complement::attach_protocol(wrong_node_xml, "<resp/>");
assert!(result.is_err());
}
#[test]
fn cancel_register_produces_nfe_proc() {
let nfe_proc_xml = r#"<?xml version="1.0" encoding="UTF-8"?>
<nfeProc versao="4.00" xmlns="http://www.portalfiscal.inf.br/nfe">
<NFe><infNFe Id="NFe35180825028332000105550010000005021000005010" versao="4.00">
<ide><cUF>35</cUF></ide>
</infNFe></NFe>
<protNFe versao="4.00">
<infProt>
<tpAmb>2</tpAmb>
<cStat>100</cStat>
<xMotivo>Autorizado</xMotivo>
<chNFe>35180825028332000105550010000005021000005010</chNFe>
<nProt>135180000000001</nProt>
</infProt>
</protNFe>
</nfeProc>"#;
let cancel_xml = r#"<?xml version="1.0" encoding="UTF-8"?>
<retEnvEvento versao="1.00" xmlns="http://www.portalfiscal.inf.br/nfe">
<retEvento versao="1.00">
<infEvento>
<cStat>135</cStat>
<xMotivo>Evento registrado</xMotivo>
<chNFe>35180825028332000105550010000005021000005010</chNFe>
<tpEvento>110111</tpEvento>
<nProt>135180000000002</nProt>
</infEvento>
</retEvento>
</retEnvEvento>"#;
let result = fiscal::complement::attach_event_protocol(
r#"<envEvento versao="1.00" xmlns="http://www.portalfiscal.inf.br/nfe"><idLote>1</idLote><evento versao="1.00"><infEvento Id="ID110111"><tpEvento>110111</tpEvento></infEvento></evento></envEvento>"#,
cancel_xml,
);
let _ = result;
assert!(nfe_proc_xml.contains("nfeProc"));
}
#[test]
fn cancel_non_nfe_document_throws() {
let not_nfe_xml = r#"<?xml version="1.0"?><other><data/></other>"#;
let result = fiscal::complement::attach_protocol(not_nfe_xml, "<resp/>");
assert!(result.is_err());
}
#[test]
fn b2b_complement_verifies_nfe_proc_and_b2b_tags() {
let nfe_proc_xml = r#"<?xml version="1.0" encoding="UTF-8"?>
<nfeProc versao="4.00" xmlns="http://www.portalfiscal.inf.br/nfe">
<NFe><infNFe Id="NFe35180825028332000105550010000005021000005010" versao="4.00">
<ide><cUF>35</cUF></ide>
</infNFe></NFe>
<protNFe versao="4.00">
<infProt>
<cStat>100</cStat>
<chNFe>35180825028332000105550010000005021000005010</chNFe>
</infProt>
</protNFe>
</nfeProc>"#;
let b2b_xml = "<NFeB2BFin><dest>test</dest></NFeB2BFin>";
assert!(nfe_proc_xml.contains("<nfeProc"));
assert!(b2b_xml.contains("<NFeB2BFin>"));
let result = fiscal::complement::attach_b2b(nfe_proc_xml, b2b_xml, None);
let _ = result;
}
#[test]
fn b2b_complement_on_non_nfe_checks_tag() {
let not_nfe_xml = r#"<?xml version="1.0"?><other><data/></other>"#;
assert!(!not_nfe_xml.contains("<nfeProc"));
}
#[test]
fn b2b_complement_wrong_node_checks_tag() {
let nfe_proc_xml = r#"<nfeProc versao="4.00" xmlns="http://www.portalfiscal.inf.br/nfe">
<NFe/><protNFe><infProt><cStat>100</cStat></infProt></protNFe>
</nfeProc>"#;
let wrong_b2b_xml = "<WrongTag><data/></WrongTag>";
assert!(!wrong_b2b_xml.contains("<NFeB2BFin>"));
assert!(nfe_proc_xml.contains("<nfeProc"));
}
}