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,255 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "logger"
|
|
4
|
+
require "stringio"
|
|
5
|
+
|
|
6
|
+
RSpec.describe FocusNfe::HTTP::Connection do
|
|
7
|
+
subject(:connection) { described_class.new(config, token: "tok") }
|
|
8
|
+
|
|
9
|
+
let(:config) { FocusNfe::Configuration.new(token_empresa: "tok", environment: environment, headers: extras) }
|
|
10
|
+
let(:environment) { :homologacao }
|
|
11
|
+
let(:extras) { {} }
|
|
12
|
+
|
|
13
|
+
def homologacao = "https://homologacao.focusnfe.com.br"
|
|
14
|
+
def producao = "https://api.focusnfe.com.br"
|
|
15
|
+
|
|
16
|
+
def authorization(token)
|
|
17
|
+
FocusNfe::HTTP::Authentication.header(token).fetch("Authorization")
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def sent_header(url, headers)
|
|
21
|
+
a_request(:get, url).with(headers: headers)
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
describe "montagem de URL" do
|
|
25
|
+
it "monta base_url + /v2/ + caminho em homologação" do
|
|
26
|
+
stub = stub_request(:get, "#{homologacao}/v2/nfe").to_return(status: 200, body: "")
|
|
27
|
+
|
|
28
|
+
connection.get("nfe")
|
|
29
|
+
|
|
30
|
+
expect(stub).to have_been_requested
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
context "quando ambiente é produção" do
|
|
34
|
+
let(:environment) { :producao }
|
|
35
|
+
|
|
36
|
+
it "usa o host de produção" do
|
|
37
|
+
stub = stub_request(:get, "#{producao}/v2/nfe").to_return(status: 200, body: "")
|
|
38
|
+
|
|
39
|
+
connection.get("nfe")
|
|
40
|
+
|
|
41
|
+
expect(stub).to have_been_requested
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
it "normaliza barra inicial no caminho" do
|
|
46
|
+
stub = stub_request(:get, "#{homologacao}/v2/nfe").to_return(status: 200, body: "")
|
|
47
|
+
|
|
48
|
+
connection.get("/nfe")
|
|
49
|
+
|
|
50
|
+
expect(stub).to have_been_requested
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
it "codifica params como query string" do
|
|
54
|
+
stub = stub_request(:get, "#{homologacao}/v2/nfe").with(query: { ref: "pedido-42" })
|
|
55
|
+
stub.to_return(status: 200, body: "")
|
|
56
|
+
|
|
57
|
+
connection.get("nfe", params: { ref: "pedido-42" })
|
|
58
|
+
|
|
59
|
+
expect(stub).to have_been_requested
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
describe "verbos" do
|
|
64
|
+
it "expõe get, post, put e delete", :aggregate_failures do
|
|
65
|
+
%i[get post put delete].each { |verb| expect(connection).to respond_to(verb) }
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
it "serializa corpo Hash para JSON no POST" do
|
|
69
|
+
stub = stub_request(:post, "#{homologacao}/v2/nfe").with(body: '{"ref":"x"}').to_return(status: 200, body: "")
|
|
70
|
+
|
|
71
|
+
connection.post("nfe", body: { ref: "x" })
|
|
72
|
+
|
|
73
|
+
expect(stub).to have_been_requested
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
it "não envia corpo quando body: é nil" do
|
|
77
|
+
stub = stub_request(:get, "#{homologacao}/v2/nfe").with { |req| req.body.nil? || req.body.empty? }
|
|
78
|
+
stub.to_return(status: 200, body: "")
|
|
79
|
+
|
|
80
|
+
connection.get("nfe")
|
|
81
|
+
|
|
82
|
+
expect(stub).to have_been_requested
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
it "envia corpo no DELETE (cancelamento com justificativa)" do
|
|
86
|
+
stub = stub_request(:delete, "#{homologacao}/v2/nfe/42").with(body: '{"justificativa":"erro"}')
|
|
87
|
+
stub.to_return(status: 200, body: "")
|
|
88
|
+
|
|
89
|
+
connection.delete("nfe/42", body: { justificativa: "erro" })
|
|
90
|
+
|
|
91
|
+
expect(stub).to have_been_requested
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
describe "cabeçalhos padrão" do
|
|
96
|
+
let(:url) { "#{homologacao}/v2/nfe" }
|
|
97
|
+
|
|
98
|
+
before { stub_request(:get, url).to_return(status: 200, body: "") }
|
|
99
|
+
|
|
100
|
+
it "envia Content-Type e Accept JSON" do
|
|
101
|
+
connection.get("nfe")
|
|
102
|
+
|
|
103
|
+
headers = { "Content-Type" => "application/json", "Accept" => "application/json" }
|
|
104
|
+
expect(sent_header(url, headers)).to have_been_made
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
it "envia User-Agent baseado em FocusNfe::VERSION" do
|
|
108
|
+
connection.get("nfe")
|
|
109
|
+
|
|
110
|
+
expect(sent_header(url, "User-Agent" => "focus_nfe/#{FocusNfe::VERSION}")).to have_been_made
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
it "envia o Authorization Basic do token" do
|
|
114
|
+
connection.get("nfe")
|
|
115
|
+
|
|
116
|
+
expect(sent_header(url, "Authorization" => authorization("tok"))).to have_been_made
|
|
117
|
+
end
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
describe "precedência de cabeçalhos" do
|
|
121
|
+
let(:url) { "#{homologacao}/v2/nfe" }
|
|
122
|
+
|
|
123
|
+
before do
|
|
124
|
+
stub_request(:get, url).to_return(status: 200, body: "")
|
|
125
|
+
stub_request(:post, url).to_return(status: 200, body: "")
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
context "com extra customizado na config" do
|
|
129
|
+
let(:extras) { { "X-Empresa" => "loja-1" } }
|
|
130
|
+
|
|
131
|
+
it "adiciona o header extra à requisição" do
|
|
132
|
+
connection.get("nfe")
|
|
133
|
+
|
|
134
|
+
expect(sent_header(url, "X-Empresa" => "loja-1")).to have_been_made
|
|
135
|
+
end
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
context "com extra tentando trocar o Authorization" do
|
|
139
|
+
let(:extras) { { "Authorization" => "Basic invasor" } }
|
|
140
|
+
|
|
141
|
+
it "mantém o Authorization calculado" do
|
|
142
|
+
connection.get("nfe")
|
|
143
|
+
|
|
144
|
+
expect(sent_header(url, "Authorization" => authorization("tok"))).to have_been_made
|
|
145
|
+
end
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
it "permite a chamada sobrescrever o Content-Type (ex.: XML)" do
|
|
149
|
+
connection.post("nfe", body: "<x/>", headers: { "Content-Type" => "application/xml" })
|
|
150
|
+
|
|
151
|
+
expect(a_request(:post, url).with(headers: { "Content-Type" => "application/xml" })).to have_been_made
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
it "ignora um Authorization per-call, mantendo o calculado" do
|
|
155
|
+
connection.get("nfe", headers: { "Authorization" => "Basic invasor" })
|
|
156
|
+
|
|
157
|
+
expect(sent_header(url, "Authorization" => authorization("tok"))).to have_been_made
|
|
158
|
+
end
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
describe "respostas" do
|
|
162
|
+
let(:url) { "#{homologacao}/v2/nfe" }
|
|
163
|
+
let(:json) { { "Content-Type" => "application/json" } }
|
|
164
|
+
|
|
165
|
+
it "devolve a Response em 2xx", :aggregate_failures do
|
|
166
|
+
stub_request(:get, url).to_return(status: 200, body: '{"ok":true}', headers: json)
|
|
167
|
+
|
|
168
|
+
response = connection.get("nfe")
|
|
169
|
+
|
|
170
|
+
expect(response).to be_a(FocusNfe::HTTP::Response)
|
|
171
|
+
expect(response.body).to eq("ok" => true)
|
|
172
|
+
end
|
|
173
|
+
|
|
174
|
+
{
|
|
175
|
+
400 => FocusNfe::Errors::BadRequest,
|
|
176
|
+
401 => FocusNfe::Errors::Unauthorized,
|
|
177
|
+
403 => FocusNfe::Errors::Forbidden,
|
|
178
|
+
404 => FocusNfe::Errors::NotFound,
|
|
179
|
+
409 => FocusNfe::Errors::Conflict,
|
|
180
|
+
422 => FocusNfe::Errors::ValidationError,
|
|
181
|
+
429 => FocusNfe::Errors::RateLimited,
|
|
182
|
+
500 => FocusNfe::Errors::ServerError,
|
|
183
|
+
418 => FocusNfe::Errors::UnexpectedResponse
|
|
184
|
+
}.each do |status, klass|
|
|
185
|
+
it "levanta #{klass} em status #{status}" do
|
|
186
|
+
stub_request(:get, url).to_return(status: status, body: "")
|
|
187
|
+
|
|
188
|
+
expect { connection.get("nfe") }.to raise_error(klass)
|
|
189
|
+
end
|
|
190
|
+
end
|
|
191
|
+
|
|
192
|
+
it "preenche a exceção com status e corpo da resposta", :aggregate_failures do
|
|
193
|
+
stub_request(:get, url).to_return(status: 422, body: '{"erro":"ref"}', headers: json)
|
|
194
|
+
|
|
195
|
+
expect { connection.get("nfe") }.to raise_error do |error|
|
|
196
|
+
expect(error).to have_attributes(status: 422, body: { "erro" => "ref" })
|
|
197
|
+
end
|
|
198
|
+
end
|
|
199
|
+
end
|
|
200
|
+
|
|
201
|
+
describe "logging" do
|
|
202
|
+
let(:url) { "#{homologacao}/v2/nfe" }
|
|
203
|
+
let(:json) { { "Content-Type" => "application/json" } }
|
|
204
|
+
let(:io) { StringIO.new }
|
|
205
|
+
let(:logger) { Logger.new(io).tap { |l| l.level = Logger::DEBUG } }
|
|
206
|
+
let(:config) do
|
|
207
|
+
FocusNfe::Configuration.new(token_empresa: "tok", environment: environment, logger: logger)
|
|
208
|
+
end
|
|
209
|
+
|
|
210
|
+
it "registra requisição em debug e resposta 2xx em info", :aggregate_failures do
|
|
211
|
+
stub_request(:get, url).to_return(status: 200, body: "")
|
|
212
|
+
|
|
213
|
+
connection.get("nfe")
|
|
214
|
+
|
|
215
|
+
expect(io.string).to match(/DEBUG.*→ GET/)
|
|
216
|
+
expect(io.string).to match(/INFO.*← 200 GET/)
|
|
217
|
+
end
|
|
218
|
+
|
|
219
|
+
it "redige o Authorization e não vaza o token", :aggregate_failures do
|
|
220
|
+
stub_request(:get, url).to_return(status: 200, body: "")
|
|
221
|
+
|
|
222
|
+
connection.get("nfe")
|
|
223
|
+
|
|
224
|
+
expect(io.string).to include("[FILTERED]")
|
|
225
|
+
expect(io.string).not_to include(authorization("tok"))
|
|
226
|
+
end
|
|
227
|
+
|
|
228
|
+
it "registra resposta não-2xx em warn com o corpo de erro", :aggregate_failures do
|
|
229
|
+
stub_request(:get, url).to_return(status: 422, body: '{"erro":"ref"}', headers: json)
|
|
230
|
+
|
|
231
|
+
expect { connection.get("nfe") }.to raise_error(FocusNfe::Errors::ValidationError)
|
|
232
|
+
expect(io.string).to match(/WARN.*← 422 GET/)
|
|
233
|
+
expect(io.string).to include("ref")
|
|
234
|
+
end
|
|
235
|
+
|
|
236
|
+
it "registra falha de transporte em error", :aggregate_failures do
|
|
237
|
+
stub_request(:get, url).to_timeout
|
|
238
|
+
|
|
239
|
+
expect { connection.get("nfe") }.to raise_error(FocusNfe::Errors::ConnectionError)
|
|
240
|
+
expect(io.string).to match(/ERROR.*✕ GET/)
|
|
241
|
+
end
|
|
242
|
+
|
|
243
|
+
context "sem logger configurado (padrão nil)" do
|
|
244
|
+
let(:config) { FocusNfe::Configuration.new(token_empresa: "tok", environment: environment) }
|
|
245
|
+
|
|
246
|
+
it "não quebra o fluxo de requisição" do
|
|
247
|
+
stub = stub_request(:get, url).to_return(status: 200, body: "")
|
|
248
|
+
|
|
249
|
+
connection.get("nfe")
|
|
250
|
+
|
|
251
|
+
expect(stub).to have_been_requested
|
|
252
|
+
end
|
|
253
|
+
end
|
|
254
|
+
end
|
|
255
|
+
end
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "logger"
|
|
4
|
+
require "stringio"
|
|
5
|
+
|
|
6
|
+
RSpec.describe FocusNfe::HTTP::Logging do
|
|
7
|
+
subject(:logging) { described_class.new(logger) }
|
|
8
|
+
|
|
9
|
+
let(:io) { StringIO.new }
|
|
10
|
+
let(:logger) { Logger.new(io).tap { |l| l.level = Logger::DEBUG } }
|
|
11
|
+
let(:output) { io.string }
|
|
12
|
+
|
|
13
|
+
let(:authorization) { FocusNfe::HTTP::Authentication.header("segredo").fetch("Authorization") }
|
|
14
|
+
let(:headers) { { "Content-Type" => "application/json", "Authorization" => authorization } }
|
|
15
|
+
|
|
16
|
+
describe "logger nil (padrão)" do
|
|
17
|
+
let(:logger) { nil }
|
|
18
|
+
|
|
19
|
+
it "é um no-op em todos os métodos", :aggregate_failures do
|
|
20
|
+
expect { logging.request(:get, "https://x/v2/nfe", headers) }.not_to raise_error
|
|
21
|
+
expect { logging.response(:get, "https://x/v2/nfe", 200, 0.1, nil) }.not_to raise_error
|
|
22
|
+
expect { logging.response(:get, "https://x/v2/nfe", 422, 0.1, '{"erro":"x"}') }.not_to raise_error
|
|
23
|
+
expect { logging.failure(:get, "https://x/v2/nfe", StandardError.new("boom"), 0.1) }.not_to raise_error
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
describe "#request" do
|
|
28
|
+
before { logging.request(:post, "https://x/v2/nfe", headers) }
|
|
29
|
+
|
|
30
|
+
it "registra em nível DEBUG com verbo e URL", :aggregate_failures do
|
|
31
|
+
expect(output).to match(/DEBUG/)
|
|
32
|
+
expect(output).to include("POST")
|
|
33
|
+
expect(output).to include("https://x/v2/nfe")
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
it "redige o Authorization e não vaza o valor real", :aggregate_failures do
|
|
37
|
+
expect(output).to include("[FILTERED]")
|
|
38
|
+
expect(output).not_to include(authorization)
|
|
39
|
+
expect(output).not_to include("Basic")
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
it "preserva headers não sensíveis" do
|
|
43
|
+
expect(output).to include("Content-Type")
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
describe "#response" do
|
|
48
|
+
it "registra 2xx em nível INFO com status e tempo, sem corpo", :aggregate_failures do
|
|
49
|
+
logging.response(:post, "https://x/v2/nfe", 200, 0.123, '{"status":"ok"}')
|
|
50
|
+
|
|
51
|
+
expect(output).to match(/INFO/)
|
|
52
|
+
expect(output).to include("200")
|
|
53
|
+
expect(output).to include("123ms")
|
|
54
|
+
expect(output).not_to include('"status":"ok"')
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
it "registra não-2xx em nível WARN incluindo o corpo de erro", :aggregate_failures do
|
|
58
|
+
logging.response(:post, "https://x/v2/nfe", 422, 0.05, '{"erro":"ref invalida"}')
|
|
59
|
+
|
|
60
|
+
expect(output).to match(/WARN/)
|
|
61
|
+
expect(output).to include("422")
|
|
62
|
+
expect(output).to include("ref invalida")
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
it "trunca corpo de erro longo em BODY_MAX" do
|
|
66
|
+
corpo = "x" * (described_class::BODY_MAX + 500)
|
|
67
|
+
|
|
68
|
+
logging.response(:post, "https://x/v2/nfe", 500, 0.01, corpo)
|
|
69
|
+
|
|
70
|
+
expect(output).not_to include("x" * (described_class::BODY_MAX + 1))
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
describe "#failure" do
|
|
75
|
+
it "registra falha de transporte em nível ERROR com a mensagem", :aggregate_failures do
|
|
76
|
+
logging.failure(:get, "https://x/v2/nfe", FocusNfe::Errors::ConnectionError.new("timeout"), 0.2)
|
|
77
|
+
|
|
78
|
+
expect(output).to match(/ERROR/)
|
|
79
|
+
expect(output).to include("timeout")
|
|
80
|
+
expect(output).to include("https://x/v2/nfe")
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
end
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
RSpec.describe FocusNfe::HTTP::Response do
|
|
4
|
+
def build(status: 200, headers: {}, body: nil)
|
|
5
|
+
described_class.new(status: status, headers: headers, body: body)
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
describe "atributos" do
|
|
9
|
+
it "expõe status, headers, body e raw_body", :aggregate_failures do
|
|
10
|
+
response = build(status: 201, headers: { "Content-Type" => "application/json" }, body: '{"ref":"abc"}')
|
|
11
|
+
|
|
12
|
+
expect(response.status).to eq(201)
|
|
13
|
+
expect(response.headers["Content-Type"]).to eq("application/json")
|
|
14
|
+
expect(response.body).to eq("ref" => "abc")
|
|
15
|
+
expect(response.raw_body).to eq('{"ref":"abc"}')
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
describe "imutabilidade" do
|
|
20
|
+
it "congela a instância" do
|
|
21
|
+
expect(build).to be_frozen
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
it "congela o conjunto de cabeçalhos" do
|
|
25
|
+
response = build(headers: { "Content-Type" => "application/json" })
|
|
26
|
+
|
|
27
|
+
expect(response.headers).to be_frozen
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
it "não expõe escritores de atributos", :aggregate_failures do
|
|
31
|
+
response = build
|
|
32
|
+
|
|
33
|
+
expect(response).not_to respond_to(:status=)
|
|
34
|
+
expect(response).not_to respond_to(:body=)
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
describe "#success?" do
|
|
39
|
+
it "é verdadeiro para status 2xx", :aggregate_failures do
|
|
40
|
+
expect(build(status: 200)).to be_success
|
|
41
|
+
expect(build(status: 204)).to be_success
|
|
42
|
+
expect(build(status: 299)).to be_success
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
it "é falso fora da faixa 2xx", :aggregate_failures do
|
|
46
|
+
expect(build(status: 199)).not_to be_success
|
|
47
|
+
expect(build(status: 301)).not_to be_success
|
|
48
|
+
expect(build(status: 404)).not_to be_success
|
|
49
|
+
expect(build(status: 500)).not_to be_success
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
describe "#body" do
|
|
54
|
+
it "parseia JSON quando o Content-Type indica JSON" do
|
|
55
|
+
response = build(
|
|
56
|
+
headers: { "Content-Type" => "application/json; charset=utf-8" },
|
|
57
|
+
body: '{"status":"autorizado","itens":[1,2]}'
|
|
58
|
+
)
|
|
59
|
+
|
|
60
|
+
expect(response.body).to eq("status" => "autorizado", "itens" => [1, 2])
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
it "parseia arrays JSON" do
|
|
64
|
+
response = build(
|
|
65
|
+
headers: { "Content-Type" => "application/json" },
|
|
66
|
+
body: "[1,2,3]"
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
expect(response.body).to eq([1, 2, 3])
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
it "devolve a string crua quando não é JSON" do
|
|
73
|
+
response = build(
|
|
74
|
+
headers: { "Content-Type" => "application/xml" },
|
|
75
|
+
body: "<nfe>...</nfe>"
|
|
76
|
+
)
|
|
77
|
+
|
|
78
|
+
expect(response.body).to eq("<nfe>...</nfe>")
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
it "devolve a string crua quando não há Content-Type" do
|
|
82
|
+
response = build(body: "texto puro")
|
|
83
|
+
|
|
84
|
+
expect(response.body).to eq("texto puro")
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
it "cai para o corpo cru quando o JSON é inválido, sem levantar" do
|
|
88
|
+
response = build(
|
|
89
|
+
headers: { "Content-Type" => "application/json" },
|
|
90
|
+
body: "{invalido"
|
|
91
|
+
)
|
|
92
|
+
|
|
93
|
+
expect(response.body).to eq("{invalido")
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
it "devolve nil quando o corpo JSON é vazio" do
|
|
97
|
+
response = build(
|
|
98
|
+
headers: { "Content-Type" => "application/json" },
|
|
99
|
+
body: ""
|
|
100
|
+
)
|
|
101
|
+
|
|
102
|
+
expect(response.body).to be_nil
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
it "é calculado uma vez (mesmo objeto a cada leitura)" do
|
|
106
|
+
response = build(
|
|
107
|
+
headers: { "Content-Type" => "application/json" },
|
|
108
|
+
body: '{"ref":"abc"}'
|
|
109
|
+
)
|
|
110
|
+
|
|
111
|
+
expect(response.body).to equal(response.body)
|
|
112
|
+
end
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
describe "#raw_body" do
|
|
116
|
+
it "devolve sempre a string original, mesmo com JSON válido" do
|
|
117
|
+
response = build(
|
|
118
|
+
headers: { "Content-Type" => "application/json" },
|
|
119
|
+
body: '{"ref":"abc"}'
|
|
120
|
+
)
|
|
121
|
+
|
|
122
|
+
expect(response.raw_body).to eq('{"ref":"abc"}')
|
|
123
|
+
end
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
describe "leitura de cabeçalhos case-insensitive" do
|
|
127
|
+
it "encontra o cabeçalho independentemente do caso usado na busca", :aggregate_failures do
|
|
128
|
+
response = build(headers: { "Content-Type" => "application/json" })
|
|
129
|
+
|
|
130
|
+
expect(response.headers["content-type"]).to eq("application/json")
|
|
131
|
+
expect(response.headers["CONTENT-TYPE"]).to eq("application/json")
|
|
132
|
+
expect(response.headers["Content-Type"]).to eq("application/json")
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
it "encontra o cabeçalho independentemente do caso recebido na origem" do
|
|
136
|
+
response = build(headers: { "content-type" => "application/json" })
|
|
137
|
+
|
|
138
|
+
expect(response.headers["Content-Type"]).to eq("application/json")
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
it "detecta JSON mesmo quando o Content-Type chega em minúsculas" do
|
|
142
|
+
response = build(
|
|
143
|
+
headers: { "content-type" => "application/json" },
|
|
144
|
+
body: '{"ok":true}'
|
|
145
|
+
)
|
|
146
|
+
|
|
147
|
+
expect(response.body).to eq("ok" => true)
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
it "devolve nil para cabeçalho ausente" do
|
|
151
|
+
expect(build.headers["X-Inexistente"]).to be_nil
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
it "expõe os cabeçalhos normalizados via #to_h", :aggregate_failures do
|
|
155
|
+
copia = build(headers: { "Content-Type" => "application/json" }).headers.to_h
|
|
156
|
+
|
|
157
|
+
expect(copia).to eq("content-type" => "application/json")
|
|
158
|
+
expect(copia).not_to be_frozen
|
|
159
|
+
end
|
|
160
|
+
end
|
|
161
|
+
end
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
RSpec.describe FocusNfe::Modelos::Documento do
|
|
4
|
+
def response(body:, status: 200)
|
|
5
|
+
FocusNfe::HTTP::Response.new(
|
|
6
|
+
status: status,
|
|
7
|
+
headers: { "Content-Type" => "application/json" },
|
|
8
|
+
body: JSON.generate(body)
|
|
9
|
+
)
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
describe ".from_response" do
|
|
13
|
+
it "mapeia status, status_sefaz e mensagem_sefaz", :aggregate_failures do
|
|
14
|
+
corpo = { "status" => "autorizado", "status_sefaz" => "100", "mensagem_sefaz" => "ok" }
|
|
15
|
+
doc = described_class.from_response(response(body: corpo))
|
|
16
|
+
|
|
17
|
+
expect(doc).to have_attributes(status: "autorizado", status_sefaz: "100", mensagem_sefaz: "ok")
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
it "mapeia chave, numero e serie", :aggregate_failures do
|
|
21
|
+
corpo = { "chave_nfe" => "3520", "numero" => "42", "serie" => "1" }
|
|
22
|
+
doc = described_class.from_response(response(body: corpo))
|
|
23
|
+
|
|
24
|
+
expect(doc).to have_attributes(chave_nfe: "3520", numero: "42", serie: "1")
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
it "mapeia os caminhos de XML e DANFe", :aggregate_failures do
|
|
28
|
+
corpo = { "caminho_xml_nota_fiscal" => "/x.xml", "caminho_danfe" => "/x.pdf" }
|
|
29
|
+
doc = described_class.from_response(response(body: corpo))
|
|
30
|
+
|
|
31
|
+
expect(doc).to have_attributes(caminho_xml_nota_fiscal: "/x.xml", caminho_danfe: "/x.pdf")
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
it "mapeia os campos da carta de correção", :aggregate_failures do
|
|
35
|
+
corpo = { "caminho_xml_carta_correcao" => "/cce.xml", "caminho_pdf_carta_correcao" => "/cce.pdf",
|
|
36
|
+
"numero_carta_correcao" => "1" }
|
|
37
|
+
doc = described_class.from_response(response(body: corpo))
|
|
38
|
+
|
|
39
|
+
expect(doc).to have_attributes(caminho_xml_carta_correcao: "/cce.xml",
|
|
40
|
+
caminho_pdf_carta_correcao: "/cce.pdf",
|
|
41
|
+
numero_carta_correcao: "1")
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
it "injeta a ref conhecida pela chamada quando o corpo não a traz" do
|
|
45
|
+
doc = described_class.from_response(response(body: { "status" => "processando_autorizacao" }), ref: "pedido-42")
|
|
46
|
+
|
|
47
|
+
expect(doc.ref).to eq("pedido-42")
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
it "guarda a resposta original para inspeção" do
|
|
51
|
+
resp = response(body: { "status" => "autorizado" })
|
|
52
|
+
|
|
53
|
+
expect(described_class.from_response(resp).response).to be(resp)
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
it "usa dados vazios quando o corpo não é um Hash", :aggregate_failures do
|
|
57
|
+
doc = described_class.from_response(response(body: ["x"]))
|
|
58
|
+
|
|
59
|
+
expect(doc.dados).to eq({})
|
|
60
|
+
expect(doc.status).to be_nil
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
describe ".from_payload" do
|
|
65
|
+
it "mapeia os campos a partir de um Hash cru", :aggregate_failures do
|
|
66
|
+
corpo = { "status" => "autorizado", "chave_nfe" => "3520", "numero" => "42" }
|
|
67
|
+
doc = described_class.from_payload(corpo)
|
|
68
|
+
|
|
69
|
+
expect(doc).to have_attributes(status: "autorizado", chave_nfe: "3520", numero: "42")
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
it "extrai a ref do próprio corpo" do
|
|
73
|
+
doc = described_class.from_payload({ "status" => "autorizado", "ref" => "pedido-42" })
|
|
74
|
+
|
|
75
|
+
expect(doc.ref).to eq("pedido-42")
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
it "injeta a ref informada quando o corpo não a traz" do
|
|
79
|
+
doc = described_class.from_payload({ "status" => "autorizado" }, ref: "pedido-42")
|
|
80
|
+
|
|
81
|
+
expect(doc.ref).to eq("pedido-42")
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
it "não guarda resposta HTTP" do
|
|
85
|
+
expect(described_class.from_payload({ "status" => "autorizado" }).response).to be_nil
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
it "usa dados vazios quando o corpo não é um Hash", :aggregate_failures do
|
|
89
|
+
doc = described_class.from_payload(["x"])
|
|
90
|
+
|
|
91
|
+
expect(doc.dados).to eq({})
|
|
92
|
+
expect(doc.status).to be_nil
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
it "congela a instância" do
|
|
96
|
+
expect(described_class.from_payload({ "status" => "autorizado" })).to be_frozen
|
|
97
|
+
end
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
describe "predicados de status" do
|
|
101
|
+
def doc(status)
|
|
102
|
+
described_class.from_response(response(body: { "status" => status }))
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
it "autorizado? é verdadeiro só para 'autorizado'", :aggregate_failures do
|
|
106
|
+
expect(doc("autorizado")).to be_autorizado
|
|
107
|
+
expect(doc("cancelado")).not_to be_autorizado
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
it "cancelado? é verdadeiro só para 'cancelado'", :aggregate_failures do
|
|
111
|
+
expect(doc("cancelado")).to be_cancelado
|
|
112
|
+
expect(doc("autorizado")).not_to be_cancelado
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
it "processando? é verdadeiro para 'processando_autorizacao'", :aggregate_failures do
|
|
116
|
+
expect(doc("processando_autorizacao")).to be_processando
|
|
117
|
+
expect(doc("autorizado")).not_to be_processando
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
it "erro? é verdadeiro para status que começam com 'erro'", :aggregate_failures do
|
|
121
|
+
expect(doc("erro_autorizacao")).to be_erro
|
|
122
|
+
expect(doc("autorizado")).not_to be_erro
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
it "denegado? é verdadeiro só para 'denegado'", :aggregate_failures do
|
|
126
|
+
expect(doc("denegado")).to be_denegado
|
|
127
|
+
expect(doc("autorizado")).not_to be_denegado
|
|
128
|
+
end
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
describe "acesso bruto" do
|
|
132
|
+
subject(:doc) do
|
|
133
|
+
described_class.from_response(response(body: { "status" => "autorizado", "extra" => "valor" }))
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
it "expõe o Hash cru via #dados" do
|
|
137
|
+
expect(doc.dados).to include("status" => "autorizado", "extra" => "valor")
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
it "delega #[] ao Hash para campos não mapeados" do
|
|
141
|
+
expect(doc["extra"]).to eq("valor")
|
|
142
|
+
end
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
describe "imutabilidade" do
|
|
146
|
+
it "congela a instância" do
|
|
147
|
+
expect(described_class.from_response(response(body: { "status" => "autorizado" }))).to be_frozen
|
|
148
|
+
end
|
|
149
|
+
end
|
|
150
|
+
end
|