focus_nfe 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.git-hooks/pre_push/steep.rb +18 -0
- data/.git-hooks/pre_push/yard_doc.rb +18 -0
- data/.gitattributes +1 -0
- data/.overcommit.yml +69 -0
- data/.rspec +3 -0
- data/.yardopts +11 -0
- data/CHANGELOG.md +77 -0
- data/CLAUDE.md +118 -0
- data/CODE_OF_CONDUCT.md +10 -0
- data/LICENSE.txt +21 -0
- data/README.md +348 -0
- data/Rakefile +105 -0
- data/data/schemas/schema_cte.json +2793 -0
- data/data/schemas/schema_cte_os.json +1335 -0
- data/data/schemas/schema_cte_os_transporte_rodoviario.json +109 -0
- data/data/schemas/schema_cte_transporte_aereo.json +115 -0
- data/data/schemas/schema_cte_transporte_aquaviario.json +174 -0
- data/data/schemas/schema_cte_transporte_dutoviario.json +65 -0
- data/data/schemas/schema_cte_transporte_ferroviario.json +144 -0
- data/data/schemas/schema_cte_transporte_multimodal.json +45 -0
- data/data/schemas/schema_cte_transporte_rodoviario.json +78 -0
- data/data/schemas/schema_dce.json +549 -0
- data/data/schemas/schema_mdfe.json +1102 -0
- data/data/schemas/schema_mdfe_transporte_aereo.json +44 -0
- data/data/schemas/schema_mdfe_transporte_aquaviario.json +209 -0
- data/data/schemas/schema_mdfe_transporte_ferroviario.json +99 -0
- data/data/schemas/schema_mdfe_transporte_rodoviario.json +628 -0
- data/data/schemas/schema_nfcom.json +1859 -0
- data/data/schemas/schema_nfe.json +4750 -0
- data/data/schemas/schema_nfe_forma_pagamento.json +97 -0
- data/data/schemas/schema_nfe_item.json +2574 -0
- data/data/schemas/schema_nfgas.json +2316 -0
- data/data/schemas/schema_nfse_nacional.json +1847 -0
- data/data/schemas/schema_nfse_recebida.json +548 -0
- data/lib/focus_nfe/client.rb +162 -0
- data/lib/focus_nfe/configuration.rb +104 -0
- data/lib/focus_nfe/errors.rb +123 -0
- data/lib/focus_nfe/esquemas/campo.rb +171 -0
- data/lib/focus_nfe/esquemas/catalogo.rb +34 -0
- data/lib/focus_nfe/esquemas/decimal.rb +66 -0
- data/lib/focus_nfe/esquemas/esquema.rb +72 -0
- data/lib/focus_nfe/esquemas/validador.rb +87 -0
- data/lib/focus_nfe/http/adapter.rb +25 -0
- data/lib/focus_nfe/http/adapters/net_http.rb +99 -0
- data/lib/focus_nfe/http/authentication.rb +23 -0
- data/lib/focus_nfe/http/connection.rb +118 -0
- data/lib/focus_nfe/http/logging.rb +100 -0
- data/lib/focus_nfe/http/response.rb +75 -0
- data/lib/focus_nfe/modelos/documento.rb +113 -0
- data/lib/focus_nfe/modelos/inutilizacao.rb +75 -0
- data/lib/focus_nfe/modelos/pagina.rb +54 -0
- data/lib/focus_nfe/recursos/backups.rb +17 -0
- data/lib/focus_nfe/recursos/base.rb +91 -0
- data/lib/focus_nfe/recursos/ceps.rb +16 -0
- data/lib/focus_nfe/recursos/cfops.rb +16 -0
- data/lib/focus_nfe/recursos/cnaes.rb +16 -0
- data/lib/focus_nfe/recursos/cnpjs.rb +13 -0
- data/lib/focus_nfe/recursos/concerns/baixavel.rb +41 -0
- data/lib/focus_nfe/recursos/concerns/baixavel_eventos.rb +34 -0
- data/lib/focus_nfe/recursos/concerns/cancelavel.rb +25 -0
- data/lib/focus_nfe/recursos/concerns/conciliavel.rb +66 -0
- data/lib/focus_nfe/recursos/concerns/consultavel.rb +26 -0
- data/lib/focus_nfe/recursos/concerns/corrigivel.rb +45 -0
- data/lib/focus_nfe/recursos/concerns/corrigivel_cte.rb +60 -0
- data/lib/focus_nfe/recursos/concerns/emitivel.rb +51 -0
- data/lib/focus_nfe/recursos/concerns/enviavel.rb +40 -0
- data/lib/focus_nfe/recursos/concerns/eventavel.rb +46 -0
- data/lib/focus_nfe/recursos/concerns/inutilizavel.rb +96 -0
- data/lib/focus_nfe/recursos/concerns/listavel.rb +22 -0
- data/lib/focus_nfe/recursos/concerns/localizavel.rb +22 -0
- data/lib/focus_nfe/recursos/concerns/notificavel.rb +23 -0
- data/lib/focus_nfe/recursos/concerns/removivel.rb +20 -0
- data/lib/focus_nfe/recursos/concerns/visualizavel.rb +28 -0
- data/lib/focus_nfe/recursos/cte.rb +35 -0
- data/lib/focus_nfe/recursos/cte_os.rb +29 -0
- data/lib/focus_nfe/recursos/ctes_recebidas.rb +38 -0
- data/lib/focus_nfe/recursos/dce.rb +16 -0
- data/lib/focus_nfe/recursos/emails_bloqueados.rb +31 -0
- data/lib/focus_nfe/recursos/empresas.rb +35 -0
- data/lib/focus_nfe/recursos/mdfe.rb +78 -0
- data/lib/focus_nfe/recursos/municipios.rb +60 -0
- data/lib/focus_nfe/recursos/ncms.rb +16 -0
- data/lib/focus_nfe/recursos/nfce.rb +19 -0
- data/lib/focus_nfe/recursos/nfcom.rb +16 -0
- data/lib/focus_nfe/recursos/nfe.rb +106 -0
- data/lib/focus_nfe/recursos/nfes_recebidas.rb +56 -0
- data/lib/focus_nfe/recursos/nfgas.rb +16 -0
- data/lib/focus_nfe/recursos/nfse.rb +18 -0
- data/lib/focus_nfe/recursos/nfse_nacional.rb +16 -0
- data/lib/focus_nfe/recursos/nfses_nacionais_recebidas.rb +21 -0
- data/lib/focus_nfe/recursos/webhooks.rb +22 -0
- data/lib/focus_nfe/version.rb +6 -0
- data/lib/focus_nfe/webhook.rb +68 -0
- data/lib/focus_nfe.rb +124 -0
- data/sig/focus_nfe/client.rbs +38 -0
- data/sig/focus_nfe/configuration.rbs +29 -0
- data/sig/focus_nfe/errors.rbs +59 -0
- data/sig/focus_nfe/esquemas/campo.rbs +47 -0
- data/sig/focus_nfe/esquemas/catalogo.rbs +8 -0
- data/sig/focus_nfe/esquemas/decimal.rbs +25 -0
- data/sig/focus_nfe/esquemas/esquema.rbs +30 -0
- data/sig/focus_nfe/esquemas/validador.rbs +20 -0
- data/sig/focus_nfe/http/adapter.rbs +7 -0
- data/sig/focus_nfe/http/adapters/net_http.rbs +25 -0
- data/sig/focus_nfe/http/authentication.rbs +9 -0
- data/sig/focus_nfe/http/connection.rbs +32 -0
- data/sig/focus_nfe/http/logging.rbs +30 -0
- data/sig/focus_nfe/http/response.rbs +28 -0
- data/sig/focus_nfe/modelos/documento.rbs +34 -0
- data/sig/focus_nfe/modelos/inutilizacao.rbs +24 -0
- data/sig/focus_nfe/modelos/pagina.rbs +21 -0
- data/sig/focus_nfe/recursos/backups.rbs +7 -0
- data/sig/focus_nfe/recursos/base.rbs +25 -0
- data/sig/focus_nfe/recursos/ceps.rbs +10 -0
- data/sig/focus_nfe/recursos/cfops.rbs +10 -0
- data/sig/focus_nfe/recursos/cnaes.rbs +10 -0
- data/sig/focus_nfe/recursos/cnpjs.rbs +7 -0
- data/sig/focus_nfe/recursos/concerns/baixavel.rbs +12 -0
- data/sig/focus_nfe/recursos/concerns/baixavel_eventos.rbs +14 -0
- data/sig/focus_nfe/recursos/concerns/cancelavel.rbs +9 -0
- data/sig/focus_nfe/recursos/concerns/conciliavel.rbs +15 -0
- data/sig/focus_nfe/recursos/concerns/consultavel.rbs +9 -0
- data/sig/focus_nfe/recursos/concerns/corrigivel.rbs +15 -0
- data/sig/focus_nfe/recursos/concerns/corrigivel_cte.rbs +17 -0
- data/sig/focus_nfe/recursos/concerns/emitivel.rbs +14 -0
- data/sig/focus_nfe/recursos/concerns/enviavel.rbs +15 -0
- data/sig/focus_nfe/recursos/concerns/eventavel.rbs +12 -0
- data/sig/focus_nfe/recursos/concerns/inutilizavel.rbs +20 -0
- data/sig/focus_nfe/recursos/concerns/listavel.rbs +9 -0
- data/sig/focus_nfe/recursos/concerns/localizavel.rbs +9 -0
- data/sig/focus_nfe/recursos/concerns/notificavel.rbs +9 -0
- data/sig/focus_nfe/recursos/concerns/removivel.rbs +9 -0
- data/sig/focus_nfe/recursos/concerns/visualizavel.rbs +9 -0
- data/sig/focus_nfe/recursos/cte.rbs +17 -0
- data/sig/focus_nfe/recursos/cte_os.rbs +17 -0
- data/sig/focus_nfe/recursos/ctes_recebidas.rbs +14 -0
- data/sig/focus_nfe/recursos/dce.rbs +10 -0
- data/sig/focus_nfe/recursos/emails_bloqueados.rbs +12 -0
- data/sig/focus_nfe/recursos/empresas.rbs +12 -0
- data/sig/focus_nfe/recursos/mdfe.rbs +21 -0
- data/sig/focus_nfe/recursos/municipios.rbs +19 -0
- data/sig/focus_nfe/recursos/ncms.rbs +10 -0
- data/sig/focus_nfe/recursos/nfce.rbs +12 -0
- data/sig/focus_nfe/recursos/nfcom.rbs +10 -0
- data/sig/focus_nfe/recursos/nfe.rbs +23 -0
- data/sig/focus_nfe/recursos/nfes_recebidas.rbs +16 -0
- data/sig/focus_nfe/recursos/nfgas.rbs +10 -0
- data/sig/focus_nfe/recursos/nfse.rbs +11 -0
- data/sig/focus_nfe/recursos/nfse_nacional.rbs +10 -0
- data/sig/focus_nfe/recursos/nfses_nacionais_recebidas.rbs +11 -0
- data/sig/focus_nfe/recursos/webhooks.rbs +11 -0
- data/sig/focus_nfe/webhook.rbs +11 -0
- data/sig/focus_nfe.rbs +10 -0
- data/spec/focus_nfe/client_spec.rb +208 -0
- data/spec/focus_nfe/configuration_spec.rb +121 -0
- data/spec/focus_nfe/errors_mapping_spec.rb +68 -0
- data/spec/focus_nfe/errors_spec.rb +107 -0
- data/spec/focus_nfe/esquemas/campo_spec.rb +291 -0
- data/spec/focus_nfe/esquemas/decimal_spec.rb +54 -0
- data/spec/focus_nfe/esquemas/esquema_spec.rb +73 -0
- data/spec/focus_nfe/esquemas/validador_spec.rb +167 -0
- data/spec/focus_nfe/esquemas_spec.rb +42 -0
- data/spec/focus_nfe/http/adapter_spec.rb +8 -0
- data/spec/focus_nfe/http/adapters/net_http_spec.rb +181 -0
- data/spec/focus_nfe/http/authentication_spec.rb +24 -0
- data/spec/focus_nfe/http/connection_spec.rb +255 -0
- data/spec/focus_nfe/http/logging_spec.rb +83 -0
- data/spec/focus_nfe/http/response_spec.rb +161 -0
- data/spec/focus_nfe/modelos/documento_spec.rb +150 -0
- data/spec/focus_nfe/modelos/inutilizacao_spec.rb +91 -0
- data/spec/focus_nfe/modelos/pagina_spec.rb +77 -0
- data/spec/focus_nfe/recursos/backups_spec.rb +29 -0
- data/spec/focus_nfe/recursos/base_spec.rb +56 -0
- data/spec/focus_nfe/recursos/ceps_spec.rb +16 -0
- data/spec/focus_nfe/recursos/cfops_spec.rb +16 -0
- data/spec/focus_nfe/recursos/cnaes_spec.rb +20 -0
- data/spec/focus_nfe/recursos/cnpjs_spec.rb +11 -0
- data/spec/focus_nfe/recursos/concerns/emitivel_validacao_spec.rb +158 -0
- data/spec/focus_nfe/recursos/cte_os_spec.rb +9 -0
- data/spec/focus_nfe/recursos/cte_spec.rb +9 -0
- data/spec/focus_nfe/recursos/ctes_recebidas_spec.rb +56 -0
- data/spec/focus_nfe/recursos/dce_spec.rb +8 -0
- data/spec/focus_nfe/recursos/emails_bloqueados_spec.rb +29 -0
- data/spec/focus_nfe/recursos/empresas_spec.rb +45 -0
- data/spec/focus_nfe/recursos/mdfe_spec.rb +100 -0
- data/spec/focus_nfe/recursos/municipios_spec.rb +58 -0
- data/spec/focus_nfe/recursos/ncms_spec.rb +16 -0
- data/spec/focus_nfe/recursos/nfce_spec.rb +10 -0
- data/spec/focus_nfe/recursos/nfcom_spec.rb +8 -0
- data/spec/focus_nfe/recursos/nfe_spec.rb +262 -0
- data/spec/focus_nfe/recursos/nfes_recebidas_spec.rb +87 -0
- data/spec/focus_nfe/recursos/nfgas_spec.rb +8 -0
- data/spec/focus_nfe/recursos/nfse_nacional_spec.rb +8 -0
- data/spec/focus_nfe/recursos/nfse_spec.rb +9 -0
- data/spec/focus_nfe/recursos/nfses_nacionais_recebidas_spec.rb +17 -0
- data/spec/focus_nfe/recursos/webhooks_spec.rb +22 -0
- data/spec/focus_nfe/webhook_spec.rb +66 -0
- data/spec/focus_nfe_global_configuration_spec.rb +70 -0
- data/spec/focus_nfe_require_spec.rb +87 -0
- data/spec/focus_nfe_spec.rb +11 -0
- data/spec/spec_helper.rb +58 -0
- data/spec/support/shared_examples/recurso_fiscal.rb +445 -0
- data/spec/support/shared_examples/recurso_leitura.rb +217 -0
- data/tools/pull_fields.rb +62 -0
- metadata +420 -0
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
RSpec.describe FocusNfe::Configuration do
|
|
4
|
+
describe "valores padrão" do
|
|
5
|
+
it "usa homologação, timeouts numéricos, tokens nil e cabeçalhos vazios" do
|
|
6
|
+
expect(described_class.new).to have_attributes(
|
|
7
|
+
token_empresa: nil, token_conta: nil, environment: :homologacao, timeout: 30,
|
|
8
|
+
open_timeout: 10, logger: nil, http_adapter: nil, headers: {}
|
|
9
|
+
)
|
|
10
|
+
end
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
describe "atributos" do
|
|
14
|
+
it "aceita todos os atributos via argumentos nomeados" do
|
|
15
|
+
logger = Object.new
|
|
16
|
+
config = described_class.new(token_empresa: "te", token_conta: "tc", environment: :producao,
|
|
17
|
+
timeout: 5, open_timeout: 2, logger: logger, headers: { "X" => "1" })
|
|
18
|
+
|
|
19
|
+
expect(config).to have_attributes(token_empresa: "te", token_conta: "tc", environment: :producao,
|
|
20
|
+
timeout: 5, open_timeout: 2, logger: logger, headers: { "X" => "1" })
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
it "permite escrever os tokens após a construção", :aggregate_failures do
|
|
24
|
+
config = described_class.new
|
|
25
|
+
config.token_empresa = "te-123"
|
|
26
|
+
config.token_conta = "tc-123"
|
|
27
|
+
|
|
28
|
+
expect(config).to have_attributes(token_empresa: "te-123", token_conta: "tc-123")
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
it "apenas armazena a referência ao logger nesta fase" do
|
|
32
|
+
logger = Object.new
|
|
33
|
+
|
|
34
|
+
expect(described_class.new(logger: logger).logger).to be(logger)
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
describe "#token_de" do
|
|
39
|
+
it "resolve o token de cada escopo", :aggregate_failures do
|
|
40
|
+
config = described_class.new(token_empresa: "te", token_conta: "tc")
|
|
41
|
+
|
|
42
|
+
expect(config.token_de(:empresa)).to eq("te")
|
|
43
|
+
expect(config.token_de(:conta)).to eq("tc")
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
describe "#base_url" do
|
|
48
|
+
it "resolve a URL de produção" do
|
|
49
|
+
expect(described_class.new(environment: :producao).base_url).to eq("https://api.focusnfe.com.br")
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
it "resolve a URL de homologação" do
|
|
53
|
+
expect(described_class.new(environment: :homologacao).base_url).to eq("https://homologacao.focusnfe.com.br")
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
it "levanta ConfigurationError para ambiente desconhecido" do
|
|
57
|
+
expect { described_class.new(environment: :sandbox).base_url }.to raise_error(FocusNfe::Errors::ConfigurationError)
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
describe "#validate!" do
|
|
62
|
+
it "levanta ConfigurationError quando nenhum token está presente" do
|
|
63
|
+
config = described_class.new(token_empresa: nil, token_conta: nil, environment: :producao)
|
|
64
|
+
|
|
65
|
+
expect { config.validate! }.to raise_error(FocusNfe::Errors::ConfigurationError, /token/)
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
it "levanta ConfigurationError quando os tokens são vazios ou só espaços" do
|
|
69
|
+
config = described_class.new(token_empresa: " ", token_conta: "", environment: :producao)
|
|
70
|
+
|
|
71
|
+
expect { config.validate! }.to raise_error(FocusNfe::Errors::ConfigurationError, /token/)
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
it "aceita apenas o token de empresa" do
|
|
75
|
+
config = described_class.new(token_empresa: "te", environment: :producao)
|
|
76
|
+
|
|
77
|
+
expect(config.validate!).to be(config)
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
it "aceita apenas o token de conta" do
|
|
81
|
+
config = described_class.new(token_conta: "tc", environment: :producao)
|
|
82
|
+
|
|
83
|
+
expect(config.validate!).to be(config)
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
it "levanta ConfigurationError quando o ambiente é desconhecido" do
|
|
87
|
+
config = described_class.new(token_empresa: "te", environment: :sandbox)
|
|
88
|
+
|
|
89
|
+
expect { config.validate! }.to raise_error(FocusNfe::Errors::ConfigurationError, /ambiente/)
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
describe "#validate_token!" do
|
|
94
|
+
it "devolve a própria configuração quando o token do escopo está presente" do
|
|
95
|
+
config = described_class.new(token_empresa: "te", token_conta: "tc")
|
|
96
|
+
|
|
97
|
+
expect(config.validate_token!(:empresa)).to be(config)
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
it "levanta ConfigurationError citando token_empresa quando o escopo :empresa não tem token" do
|
|
101
|
+
config = described_class.new(token_conta: "tc")
|
|
102
|
+
|
|
103
|
+
expect { config.validate_token!(:empresa) }
|
|
104
|
+
.to raise_error(FocusNfe::Errors::ConfigurationError, /token_empresa/)
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
it "levanta ConfigurationError citando token_conta quando o escopo :conta não tem token" do
|
|
108
|
+
config = described_class.new(token_empresa: "te")
|
|
109
|
+
|
|
110
|
+
expect { config.validate_token!(:conta) }
|
|
111
|
+
.to raise_error(FocusNfe::Errors::ConfigurationError, /token_conta/)
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
it "valida o ambiente antes do token" do
|
|
115
|
+
config = described_class.new(token_empresa: "te", environment: :sandbox)
|
|
116
|
+
|
|
117
|
+
expect { config.validate_token!(:empresa) }
|
|
118
|
+
.to raise_error(FocusNfe::Errors::ConfigurationError, /ambiente/)
|
|
119
|
+
end
|
|
120
|
+
end
|
|
121
|
+
end
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
RSpec.describe FocusNfe::Errors do
|
|
4
|
+
describe ".class_for" do
|
|
5
|
+
{
|
|
6
|
+
400 => FocusNfe::Errors::BadRequest,
|
|
7
|
+
401 => FocusNfe::Errors::Unauthorized,
|
|
8
|
+
403 => FocusNfe::Errors::Forbidden,
|
|
9
|
+
404 => FocusNfe::Errors::NotFound,
|
|
10
|
+
409 => FocusNfe::Errors::Conflict,
|
|
11
|
+
422 => FocusNfe::Errors::ValidationError,
|
|
12
|
+
429 => FocusNfe::Errors::RateLimited
|
|
13
|
+
}.each do |status, klass|
|
|
14
|
+
it "mapeia #{status} para #{klass}" do
|
|
15
|
+
expect(described_class.class_for(status)).to eq(klass)
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
it "mapeia qualquer 5xx para ServerError", :aggregate_failures do
|
|
20
|
+
expect(described_class.class_for(500)).to eq(described_class::ServerError)
|
|
21
|
+
expect(described_class.class_for(503)).to eq(described_class::ServerError)
|
|
22
|
+
expect(described_class.class_for(599)).to eq(described_class::ServerError)
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
it "mapeia status não-2xx não previstos para UnexpectedResponse", :aggregate_failures do
|
|
26
|
+
expect(described_class.class_for(418)).to eq(described_class::UnexpectedResponse)
|
|
27
|
+
expect(described_class.class_for(451)).to eq(described_class::UnexpectedResponse)
|
|
28
|
+
expect(described_class.class_for(300)).to eq(described_class::UnexpectedResponse)
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
describe ".from_response" do
|
|
33
|
+
def response(status:, body: nil)
|
|
34
|
+
FocusNfe::HTTP::Response.new(
|
|
35
|
+
status: status,
|
|
36
|
+
headers: { "Content-Type" => "application/json" },
|
|
37
|
+
body: body
|
|
38
|
+
)
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
it "instancia a classe correta conforme o status" do
|
|
42
|
+
error = described_class.from_response(response(status: 422))
|
|
43
|
+
|
|
44
|
+
expect(error).to be_a(described_class::ValidationError)
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
it "preenche status, corpo e resposta a partir da Response", :aggregate_failures do
|
|
48
|
+
original = response(status: 422, body: '{"mensagem":"ref inválida"}')
|
|
49
|
+
error = described_class.from_response(original)
|
|
50
|
+
|
|
51
|
+
expect(error.status).to eq(422)
|
|
52
|
+
expect(error.body).to eq("mensagem" => "ref inválida")
|
|
53
|
+
expect(error.response).to be(original)
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
it "usa UnexpectedResponse para status não mapeado" do
|
|
57
|
+
error = described_class.from_response(response(status: 418))
|
|
58
|
+
|
|
59
|
+
expect(error).to be_a(described_class::UnexpectedResponse)
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
it "produz uma exceção levantável que carrega o status na mensagem" do
|
|
63
|
+
error = described_class.from_response(response(status: 500))
|
|
64
|
+
|
|
65
|
+
expect { raise error }.to raise_error(described_class::ServerError, /500/)
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
end
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
RSpec.describe FocusNfe::Errors do
|
|
4
|
+
describe "FocusNfe::Error (raiz da hierarquia)" do
|
|
5
|
+
it "descende de StandardError" do
|
|
6
|
+
expect(FocusNfe::Error.new).to be_a(StandardError)
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
it "substitui a antiga FocusNfe::Erro (português), que deixa de existir" do
|
|
10
|
+
expect(FocusNfe.const_defined?(:Erro, false)).to be(false)
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
describe FocusNfe::Errors::HttpError do
|
|
15
|
+
it "descende de FocusNfe::Error" do
|
|
16
|
+
expect(described_class.ancestors).to include(FocusNfe::Error)
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
it "expõe status, corpo e resposta informados na construção" do
|
|
20
|
+
response = Object.new
|
|
21
|
+
error = described_class.new("falhou", status: 422, body: { "msg" => "ref" }, response: response)
|
|
22
|
+
|
|
23
|
+
expect(error).to have_attributes(message: "falhou", status: 422, body: { "msg" => "ref" }, response: response)
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
it "pode ser construído sem argumentos, com leitores nil", :aggregate_failures do
|
|
27
|
+
error = described_class.new
|
|
28
|
+
|
|
29
|
+
expect(error.status).to be_nil
|
|
30
|
+
expect(error.body).to be_nil
|
|
31
|
+
expect(error.response).to be_nil
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
describe "#codigo" do
|
|
35
|
+
it "extrai o código do corpo estruturado" do
|
|
36
|
+
error = described_class.new(body: { "codigo" => "nao_encontrado", "mensagem" => "..." })
|
|
37
|
+
|
|
38
|
+
expect(error.codigo).to eq("nao_encontrado")
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
it "é nil quando o corpo não traz codigo, é String ou é nil", :aggregate_failures do
|
|
42
|
+
expect(described_class.new(body: { "mensagem" => "..." }).codigo).to be_nil
|
|
43
|
+
expect(described_class.new(body: "<html>500</html>").codigo).to be_nil
|
|
44
|
+
expect(described_class.new(body: nil).codigo).to be_nil
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
describe "#erros" do
|
|
49
|
+
it "devolve a lista de validação como está" do
|
|
50
|
+
lista = [{ "campo" => "natureza_operacao", "mensagem" => "não pode ficar em branco" }]
|
|
51
|
+
error = described_class.new(body: { "erros" => lista })
|
|
52
|
+
|
|
53
|
+
expect(error.erros).to eq(lista)
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
it "embrulha o erro único no formato { campo, mensagem }" do
|
|
57
|
+
error = described_class.new(body: { "codigo" => "x", "mensagem" => "A NFe não está autorizada" })
|
|
58
|
+
|
|
59
|
+
expect(error.erros).to eq([{ "campo" => nil, "mensagem" => "A NFe não está autorizada" }])
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
it "é vazio sem erros nem mensagem, ou com corpo não-Hash", :aggregate_failures do
|
|
63
|
+
expect(described_class.new(body: { "codigo" => "x" }).erros).to eq([])
|
|
64
|
+
expect(described_class.new(body: "<html>500</html>").erros).to eq([])
|
|
65
|
+
expect(described_class.new(body: nil).erros).to eq([])
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
describe "subclasses tipadas de HttpError" do
|
|
71
|
+
%i[
|
|
72
|
+
BadRequest Unauthorized Forbidden NotFound Conflict
|
|
73
|
+
ValidationError RateLimited ServerError UnexpectedResponse
|
|
74
|
+
].each do |name|
|
|
75
|
+
it "#{name} descende de HttpError, Error e StandardError", :aggregate_failures do
|
|
76
|
+
klass = described_class.const_get(name)
|
|
77
|
+
|
|
78
|
+
expect(klass.ancestors).to include(FocusNfe::Errors::HttpError)
|
|
79
|
+
expect(klass.ancestors).to include(FocusNfe::Error)
|
|
80
|
+
expect(klass.ancestors).to include(StandardError)
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
it "reúnem exatamente 9 classes distintas, sem aliasing" do
|
|
85
|
+
todas = described_class.constants.map { |name| described_class.const_get(name) }
|
|
86
|
+
subclasses = todas.select { |const| const.is_a?(Class) && const < described_class::HttpError }
|
|
87
|
+
|
|
88
|
+
expect(subclasses.uniq.size).to eq(9)
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
describe "erros não-HTTP (client-side e transporte)" do
|
|
93
|
+
it "ConfigurationError descende de Error, mas não de HttpError", :aggregate_failures do
|
|
94
|
+
ancestrais = FocusNfe::Errors::ConfigurationError.ancestors
|
|
95
|
+
|
|
96
|
+
expect(ancestrais).to include(FocusNfe::Error)
|
|
97
|
+
expect(ancestrais).not_to include(FocusNfe::Errors::HttpError)
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
it "ConnectionError descende de Error, mas não de HttpError", :aggregate_failures do
|
|
101
|
+
ancestrais = FocusNfe::Errors::ConnectionError.ancestors
|
|
102
|
+
|
|
103
|
+
expect(ancestrais).to include(FocusNfe::Error)
|
|
104
|
+
expect(ancestrais).not_to include(FocusNfe::Errors::HttpError)
|
|
105
|
+
end
|
|
106
|
+
end
|
|
107
|
+
end
|
|
@@ -0,0 +1,291 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
RSpec.describe FocusNfe::Esquemas::Campo do
|
|
4
|
+
def campo(atributos)
|
|
5
|
+
described_class.new(atributos)
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
describe "#nome e #obrigatorio?" do
|
|
9
|
+
it "lê o nome e a obrigatoriedade da definição", :aggregate_failures do
|
|
10
|
+
c = campo("name" => "natureza_operacao", "type" => "String[1-60]", "required" => true)
|
|
11
|
+
|
|
12
|
+
expect(c.nome).to eq("natureza_operacao")
|
|
13
|
+
expect(c).to be_obrigatorio
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
it "não é obrigatório quando required é false" do
|
|
17
|
+
expect(campo("name" => "x", "type" => "String[1-60]", "required" => false)).not_to be_obrigatorio
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
describe "parsing do tipo" do
|
|
22
|
+
it "interpreta String[1-60] como string com mínimo e máximo", :aggregate_failures do
|
|
23
|
+
c = campo("name" => "x", "type" => "String[1-60]")
|
|
24
|
+
|
|
25
|
+
expect(c.tipo).to eq(:string)
|
|
26
|
+
expect(c.tamanho_minimo).to eq(1)
|
|
27
|
+
expect(c.tamanho_maximo).to eq(60)
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
it "interpreta String[14] como tamanho fixo", :aggregate_failures do
|
|
31
|
+
c = campo("name" => "x", "type" => "String[14]")
|
|
32
|
+
|
|
33
|
+
expect(c.tipo).to eq(:string)
|
|
34
|
+
expect(c.tamanho_minimo).to eq(14)
|
|
35
|
+
expect(c.tamanho_maximo).to eq(14)
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
it "interpreta Integer[1-9] como inteiro com faixa de dígitos", :aggregate_failures do
|
|
39
|
+
c = campo("name" => "x", "type" => "Integer[1-9]")
|
|
40
|
+
|
|
41
|
+
expect(c.tipo).to eq(:integer)
|
|
42
|
+
expect(c.tamanho_minimo).to eq(1)
|
|
43
|
+
expect(c.tamanho_maximo).to eq(9)
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
it "interpreta Decimal[13.2] como inteiros e casas fixas", :aggregate_failures do
|
|
47
|
+
c = campo("name" => "x", "type" => "Decimal[13.2]")
|
|
48
|
+
|
|
49
|
+
expect(c.tipo).to eq(:decimal)
|
|
50
|
+
expect(c.decimal.inteiros).to eq(13)
|
|
51
|
+
expect(c.decimal.casas_minimas).to eq(2)
|
|
52
|
+
expect(c.decimal.casas_maximas).to eq(2)
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
it "interpreta Decimal[13.2-4] como faixa de casas", :aggregate_failures do
|
|
56
|
+
c = campo("name" => "x", "type" => "Decimal[13.2-4]")
|
|
57
|
+
|
|
58
|
+
expect(c.decimal.casas_minimas).to eq(2)
|
|
59
|
+
expect(c.decimal.casas_maximas).to eq(4)
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
it "interpreta Decimal[13.] como zero casas", :aggregate_failures do
|
|
63
|
+
c = campo("name" => "x", "type" => "Decimal[13.]")
|
|
64
|
+
|
|
65
|
+
expect(c.decimal.inteiros).to eq(13)
|
|
66
|
+
expect(c.decimal.casas_minimas).to eq(0)
|
|
67
|
+
expect(c.decimal.casas_maximas).to eq(0)
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
it "interpreta Decimal[2] sem ponto como zero casas", :aggregate_failures do
|
|
71
|
+
c = campo("name" => "x", "type" => "Decimal[2]")
|
|
72
|
+
|
|
73
|
+
expect(c.decimal.inteiros).to eq(2)
|
|
74
|
+
expect(c.decimal.casas_maximas).to eq(0)
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
it "interpreta Decimal[11.0-10] com casas a partir de zero", :aggregate_failures do
|
|
78
|
+
c = campo("name" => "x", "type" => "Decimal[11.0-10]")
|
|
79
|
+
|
|
80
|
+
expect(c.decimal.inteiros).to eq(11)
|
|
81
|
+
expect(c.decimal.casas_minimas).to eq(0)
|
|
82
|
+
expect(c.decimal.casas_maximas).to eq(10)
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
it "interpreta DateTime" do
|
|
86
|
+
expect(campo("name" => "x", "type" => "DateTime").tipo).to eq(:datetime)
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
it "interpreta Date" do
|
|
90
|
+
expect(campo("name" => "x", "type" => "Date").tipo).to eq(:date)
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
it "interpreta type nulo como enum quando há enum" do
|
|
94
|
+
c = campo("name" => "x", "type" => nil, "enum" => "* +1+: Sim")
|
|
95
|
+
|
|
96
|
+
expect(c.tipo).to eq(:enum)
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
it "marca coleções", :aggregate_failures do
|
|
100
|
+
c = campo("name" => "items", "type" => "Coleção[1-990]", "collection" => { "object_attributes" => [] })
|
|
101
|
+
|
|
102
|
+
expect(c).to be_colecao
|
|
103
|
+
expect(c.tipo).to eq(:colecao)
|
|
104
|
+
end
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
describe "#esquema_colecao" do
|
|
108
|
+
let(:atributos) do
|
|
109
|
+
[
|
|
110
|
+
{ "name" => "numero", "type" => "Integer[1-3]", "required" => true },
|
|
111
|
+
{ "name" => "descricao", "type" => "String[1-120]", "required" => true }
|
|
112
|
+
]
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
it "devolve nil para campo escalar" do
|
|
116
|
+
expect(campo("name" => "x", "type" => "String[1-60]").esquema_colecao).to be_nil
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
it "devolve nil para coleção sem object_attributes" do
|
|
120
|
+
expect(campo("name" => "x", "type" => "Coleção[0-5]", "collection" => {}).esquema_colecao).to be_nil
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
it "constrói um Esquema com os subcampos da coleção" do
|
|
124
|
+
c = campo("name" => "items", "type" => "Coleção[1-990]", "collection" => { "object_attributes" => atributos })
|
|
125
|
+
|
|
126
|
+
expect(c.esquema_colecao.campos.map(&:nome)).to eq(%w[numero descricao])
|
|
127
|
+
end
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
describe "#to_h" do
|
|
131
|
+
it "descreve um campo escalar com tipo, tamanho e metadados", :aggregate_failures do
|
|
132
|
+
h = campo(
|
|
133
|
+
"name" => "natureza_operacao", "description" => "Descrição da natureza de operação.",
|
|
134
|
+
"type" => "String[1-60]", "required" => true, "tag" => "natOp"
|
|
135
|
+
).to_h
|
|
136
|
+
|
|
137
|
+
expect(h).to include(
|
|
138
|
+
nome: "natureza_operacao", descricao: "Descrição da natureza de operação.",
|
|
139
|
+
tipo: :string, tipo_bruto: "String[1-60]", obrigatorio: true,
|
|
140
|
+
tamanho_minimo: 1, tamanho_maximo: 60, enum: nil, tag: "natOp", colecao: nil
|
|
141
|
+
)
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
it "expõe o enum e marca o tipo :enum", :aggregate_failures do
|
|
145
|
+
h = campo("name" => "tipo_documento", "type" => nil, "enum" => "* +1+: Sim").to_h
|
|
146
|
+
|
|
147
|
+
expect(h[:tipo]).to eq(:enum)
|
|
148
|
+
expect(h[:enum]).to eq("* +1+: Sim")
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
it "aninha os subcampos de uma coleção em :colecao", :aggregate_failures do
|
|
152
|
+
atributos = [{ "name" => "numero", "type" => "Integer[1-3]", "required" => true }]
|
|
153
|
+
colecao = { "object_attributes" => atributos }
|
|
154
|
+
h = campo("name" => "items", "type" => "Coleção[1-990]", "collection" => colecao).to_h
|
|
155
|
+
|
|
156
|
+
expect(h[:tipo]).to eq(:colecao)
|
|
157
|
+
expect(h[:colecao]).to eq([campo(atributos.first).to_h])
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
it "deixa :colecao nil para coleção sem object_attributes" do
|
|
161
|
+
h = campo("name" => "x", "type" => "Coleção[0-5]", "collection" => {}).to_h
|
|
162
|
+
|
|
163
|
+
expect(h[:colecao]).to be_nil
|
|
164
|
+
end
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
describe "#validar_valor" do
|
|
168
|
+
it "aceita string dentro do tamanho" do
|
|
169
|
+
expect(campo("name" => "x", "type" => "String[1-60]").validar_valor("ok")).to be_nil
|
|
170
|
+
end
|
|
171
|
+
|
|
172
|
+
it "rejeita string acima do máximo" do
|
|
173
|
+
mensagem = campo("name" => "natureza_operacao", "type" => "String[1-3]").validar_valor("abcd")
|
|
174
|
+
|
|
175
|
+
expect(mensagem).to include("natureza_operacao")
|
|
176
|
+
end
|
|
177
|
+
|
|
178
|
+
it "rejeita inteiro com não-dígitos" do
|
|
179
|
+
expect(campo("name" => "numero", "type" => "Integer[1-9]").validar_valor("12a")).to include("numero")
|
|
180
|
+
end
|
|
181
|
+
|
|
182
|
+
it "aceita inteiro válido na faixa de dígitos" do
|
|
183
|
+
expect(campo("name" => "numero", "type" => "Integer[1-9]").validar_valor(12_345)).to be_nil
|
|
184
|
+
end
|
|
185
|
+
|
|
186
|
+
it "rejeita inteiro com quantidade de dígitos fora da faixa", :aggregate_failures do
|
|
187
|
+
mensagem = campo("name" => "numero", "type" => "Integer[1-3]").validar_valor(12_345)
|
|
188
|
+
|
|
189
|
+
expect(mensagem).to include("numero")
|
|
190
|
+
expect(mensagem).to include("5 dígitos")
|
|
191
|
+
end
|
|
192
|
+
|
|
193
|
+
it "não restringe coleções nem tipos desconhecidos", :aggregate_failures do
|
|
194
|
+
expect(campo("name" => "x", "type" => "Coleção[0-5]", "collection" => {}).validar_valor([])).to be_nil
|
|
195
|
+
expect(campo("name" => "x", "type" => "Algo[1-2]").validar_valor("qualquer")).to be_nil
|
|
196
|
+
end
|
|
197
|
+
|
|
198
|
+
context "com decimais" do
|
|
199
|
+
it "aceita decimal dentro de inteiros e casas" do
|
|
200
|
+
expect(campo("name" => "valor", "type" => "Decimal[13.2]").validar_valor("10.50")).to be_nil
|
|
201
|
+
end
|
|
202
|
+
|
|
203
|
+
it "aceita menos casas que o mínimo" do
|
|
204
|
+
expect(campo("name" => "valor", "type" => "Decimal[13.2]").validar_valor("10")).to be_nil
|
|
205
|
+
end
|
|
206
|
+
|
|
207
|
+
it "aceita Numeric além de String" do
|
|
208
|
+
expect(campo("name" => "valor", "type" => "Decimal[13.2]").validar_valor(10.5)).to be_nil
|
|
209
|
+
end
|
|
210
|
+
|
|
211
|
+
it "rejeita inteiros demais", :aggregate_failures do
|
|
212
|
+
mensagem = campo("name" => "valor", "type" => "Decimal[2.2]").validar_valor("1234.5")
|
|
213
|
+
|
|
214
|
+
expect(mensagem).to include("valor")
|
|
215
|
+
end
|
|
216
|
+
|
|
217
|
+
it "rejeita casas decimais além do máximo" do
|
|
218
|
+
expect(campo("name" => "valor", "type" => "Decimal[13.2]").validar_valor("10.123")).to include("valor")
|
|
219
|
+
end
|
|
220
|
+
|
|
221
|
+
it "rejeita valor não numérico" do
|
|
222
|
+
expect(campo("name" => "valor", "type" => "Decimal[13.2]").validar_valor("abc")).to include("valor")
|
|
223
|
+
end
|
|
224
|
+
end
|
|
225
|
+
|
|
226
|
+
context "com enums" do
|
|
227
|
+
it "aceita valor dentro do conjunto declarado" do
|
|
228
|
+
expect(campo("name" => "modalidade", "type" => nil, "enum" => "* +1+: Sim").validar_valor("1")).to be_nil
|
|
229
|
+
end
|
|
230
|
+
|
|
231
|
+
it "aceita Integer correspondente ao código String" do
|
|
232
|
+
expect(campo("name" => "modalidade", "type" => nil, "enum" => "* +1+: Sim").validar_valor(1)).to be_nil
|
|
233
|
+
end
|
|
234
|
+
|
|
235
|
+
it "rejeita valor fora do conjunto" do
|
|
236
|
+
c = campo("name" => "modalidade", "type" => nil, "enum" => "* +0+: Não\\n* +1+: Sim")
|
|
237
|
+
|
|
238
|
+
expect(c.validar_valor("9")).to include("modalidade")
|
|
239
|
+
end
|
|
240
|
+
|
|
241
|
+
it "valida o conjunto mesmo quando o campo também tem tipo escalar" do
|
|
242
|
+
c = campo("name" => "indicador", "type" => "Integer[1-1]", "enum" => "* +0+: Não\\n* +1+: Sim")
|
|
243
|
+
|
|
244
|
+
expect(c.validar_valor(9)).to include("indicador")
|
|
245
|
+
end
|
|
246
|
+
|
|
247
|
+
it "rejeita o tipo escalar antes do enum" do
|
|
248
|
+
c = campo("name" => "indicador", "type" => "Integer[1-1]", "enum" => "* +1+: Sim")
|
|
249
|
+
|
|
250
|
+
expect(c.validar_valor("x")).to include("indicador")
|
|
251
|
+
end
|
|
252
|
+
end
|
|
253
|
+
|
|
254
|
+
context "com datas" do
|
|
255
|
+
it "aceita Date em ISO 8601" do
|
|
256
|
+
expect(campo("name" => "data", "type" => "Date").validar_valor("2026-06-14")).to be_nil
|
|
257
|
+
end
|
|
258
|
+
|
|
259
|
+
it "rejeita Date inválida" do
|
|
260
|
+
expect(campo("name" => "data", "type" => "Date").validar_valor("14/06/2026")).to include("data")
|
|
261
|
+
end
|
|
262
|
+
|
|
263
|
+
it "aceita DateTime com offset" do
|
|
264
|
+
expect(campo("name" => "emissao", "type" => "DateTime").validar_valor("2026-06-14T10:00:00-03:00")).to be_nil
|
|
265
|
+
end
|
|
266
|
+
|
|
267
|
+
it "rejeita DateTime inválido" do
|
|
268
|
+
expect(campo("name" => "emissao", "type" => "DateTime").validar_valor("qualquer")).to include("emissao")
|
|
269
|
+
end
|
|
270
|
+
end
|
|
271
|
+
end
|
|
272
|
+
|
|
273
|
+
describe "#valores_enum" do
|
|
274
|
+
it "extrai códigos do formato com espaço" do
|
|
275
|
+
c = campo("name" => "x", "type" => nil, "enum" => "* +0+: Correios\\n* +1+: Conta própria")
|
|
276
|
+
|
|
277
|
+
expect(c.valores_enum).to eq(%w[0 1])
|
|
278
|
+
end
|
|
279
|
+
|
|
280
|
+
it "extrai códigos do formato sem espaço e multi-caractere" do
|
|
281
|
+
c = campo("name" => "x", "type" => nil, "enum" => "*+01+: Repasse\\n*+99+: Outros")
|
|
282
|
+
|
|
283
|
+
expect(c.valores_enum).to eq(%w[01 99])
|
|
284
|
+
end
|
|
285
|
+
|
|
286
|
+
it "devolve vazio para enum em branco", :aggregate_failures do
|
|
287
|
+
expect(campo("name" => "x", "type" => "String[1-2]", "enum" => "").valores_enum).to eq([])
|
|
288
|
+
expect(campo("name" => "x", "type" => "String[1-2]").enum?).to be(false)
|
|
289
|
+
end
|
|
290
|
+
end
|
|
291
|
+
end
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
RSpec.describe FocusNfe::Esquemas::Decimal do
|
|
4
|
+
describe ".parsear" do
|
|
5
|
+
it "devolve nil para tipo que não é decimal", :aggregate_failures do
|
|
6
|
+
expect(described_class.parsear(nil)).to be_nil
|
|
7
|
+
expect(described_class.parsear("String[1-60]")).to be_nil
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
it "parseia inteiros e a faixa de casas", :aggregate_failures do
|
|
11
|
+
d = described_class.parsear("Decimal[13.2-4]")
|
|
12
|
+
|
|
13
|
+
expect(d.inteiros).to eq(13)
|
|
14
|
+
expect(d.casas_minimas).to eq(2)
|
|
15
|
+
expect(d.casas_maximas).to eq(4)
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
it "trata ponto sem casas como zero casas" do
|
|
19
|
+
expect(described_class.parsear("Decimal[13.]").casas_maximas).to eq(0)
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
describe "#validar" do
|
|
24
|
+
subject(:decimal) { described_class.parsear("Decimal[13.2]") }
|
|
25
|
+
|
|
26
|
+
it "aceita valor dentro de inteiros e casas" do
|
|
27
|
+
expect(decimal.validar("10.50")).to be_nil
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
it "aceita menos casas que o mínimo" do
|
|
31
|
+
expect(decimal.validar("10")).to be_nil
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
it "rejeita inteiros demais" do
|
|
35
|
+
expect(described_class.parsear("Decimal[2.2]").validar("1234.5")).to include("inteiros")
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
it "rejeita casas decimais além do máximo" do
|
|
39
|
+
expect(decimal.validar("10.123")).to include("casas")
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
it "rejeita valor não numérico" do
|
|
43
|
+
expect(decimal.validar("abc")).to include("inválido")
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
describe "#to_h" do
|
|
48
|
+
it "descreve a especificação" do
|
|
49
|
+
expect(described_class.parsear("Decimal[13.2-4]").to_h).to eq(
|
|
50
|
+
inteiros: 13, casas_minimas: 2, casas_maximas: 4
|
|
51
|
+
)
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|