arca.rb 1.0.2 → 1.1.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 81ac40aba0c7a3e67dc24402f3888da7af56ab40d785511da1ca310b2a3054e5
4
- data.tar.gz: 41b29c0590307f836ccbe05d57d6f291382f0c545b6a8f1161533de6fcb24622
3
+ metadata.gz: dd4d89db0b9d4d77abb2b5e6eb0e03d3169893d511a0e2b066f12e3c1d613e2c
4
+ data.tar.gz: 49b8a96793606351fa4a9ca0b137cab1f7efc2459ef5604d9efffe0a20c5b54f
5
5
  SHA512:
6
- metadata.gz: ca0895cc00a9f1ebe502fb3021f1305b9073ce1d28b5bab672cac50e124ee2639d88f472659ad17819b6295069e6a28d10603cd48e5d2390eb7fd6b9fe69c03f
7
- data.tar.gz: 61c8e280fdd2f2d7e177e21fba6ddd5f2b46e25c1f98a98f93ead854e3ba747eb6c935723c5b02968c87913441152d7cdfd794369ac0ca7f6bd4c87d20542384
6
+ metadata.gz: 4b6060fe8e15d82e5ca2cf8cfa5f7651899ae10661df16a389b6cb43ddcfbed79f6303b17a126b654d40e1b5830ba78263f6a20f7af6d11a1fe36f89eee8c459
7
+ data.tar.gz: 02242344d53082a0b5b7f3b6aba1f004184b8aa8ac64e91ac9ed07b7f623a95191fe82582f0acd14256d616d5b2928f59b07471c73351887dcf7fb4b8af7342f
data/CHANGELOG.md CHANGED
@@ -1,5 +1,9 @@
1
1
  # Arca Changelog
2
2
 
3
+ ## [1.1.0]
4
+
5
+ - **WSAA:** Store opcional para TA. Podés pasar `store:` con un objeto que implemente `read(cuit:, env:, service:)` y `write(cuit:, env:, service:, ta:, expires_at:)` para guardar el token en tu backend (p. ej. DB por tenant con cifrado). Sin `store`, se sigue usando el archivo JSON en `tmp/`.
6
+
3
7
  ## [1.0.2] - 2025-02-09
4
8
 
5
9
  - **Errores:** En WSAA, `persist_ta` ahora encapsula fallos en `Arca::ServerError` en lugar de re-levantar excepciones crudas (mejor encapsulación y menos riesgo de filtrar datos sensibles en logs).
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- arca.rb (1.0.2)
4
+ arca.rb (1.1.0)
5
5
  activesupport
6
6
  builder
7
7
  httpclient
data/LICENSE CHANGED
@@ -1,4 +1,4 @@
1
- Copyright (c) 2025 ARCA Kit
1
+ Copyright (c) 2025 driveton
2
2
 
3
3
  MIT License
4
4
 
data/README.md CHANGED
@@ -75,7 +75,7 @@ Opción del constructor: `env: :development` o `env: :production` (symbol o stri
75
75
 
76
76
  ## Uso
77
77
 
78
- Opciones comunes del constructor: `env`, `cuit`, `key`, `cert`. Opcionales: `savon` (hash pasado a Savon), `ta_path` (WSAA), etc.
78
+ Opciones comunes del constructor: `env`, `cuit`, `key`, `cert`. Opcionales: `savon` (hash pasado a Savon), `ta_path` (WSAA, path del archivo TA), `store` (WSAA, objeto con `read`/`write` para guardar TA en backend propio), etc.
79
79
 
80
80
  ### Cliente de bajo nivel (`Arca::Client`)
81
81
 
@@ -112,6 +112,11 @@ wsaa = Arca::WSAA.new(env: :development, cuit: '20123456789', key: key, cert: ce
112
112
  auth = wsaa.auth # => { token: '...', sign: '...' }
113
113
  ```
114
114
 
115
+ Opcionalmente podés pasar un **store** para guardar el TA en tu propio backend (por ejemplo DB por tenant con cifrado). Sin `store`, se usa un archivo JSON en `tmp/`. El store debe implementar:
116
+
117
+ - **`read(cuit:, env:, service:)`** — devuelve `nil` o un Hash con claves simbólicas: `:token`, `:sign`, `:generation_time`, `:expiration_time` (Time o string ISO8601).
118
+ - **`write(cuit:, env:, service:, ta:, expires_at:)`** — se invoca después de un `#login` exitoso; `ta` es el hash del TA y `expires_at` el Time de vencimiento.
119
+
115
120
  ### WSFE (Factura Electrónica)
116
121
  ```ruby
117
122
  ws = Arca::WSFE.new(env: :development, cuit: '20123456789', key: key, cert: cert)
data/lib/arca/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Arca
4
- VERSION = "1.0.2"
4
+ VERSION = "1.1.0"
5
5
  end
data/lib/arca/wsaa.rb CHANGED
@@ -4,7 +4,7 @@ require "json"
4
4
 
5
5
  module Arca
6
6
  class WSAA
7
- attr_reader :key, :cert, :service, :ta, :cuit, :client, :env
7
+ attr_reader :key, :cert, :service, :ta, :cuit, :client, :env, :store
8
8
 
9
9
  WSDL = {
10
10
  development: "https://wsaahomo.afip.gov.ar/ws/services/LoginCms?wsdl",
@@ -20,6 +20,7 @@ module Arca
20
20
  @ttl = options[:ttl] || 2400
21
21
  @cuit = options[:cuit]
22
22
  @client = Client.new Hash(options[:savon]).reverse_merge(wsdl: WSDL[@env])
23
+ @store = options[:store]
23
24
  @ta_path = options[:ta_path] || default_ta_path
24
25
  end
25
26
 
@@ -79,6 +80,10 @@ module Arca
79
80
  end
80
81
 
81
82
  def restore_ta
83
+ if @store
84
+ return restore_ta_from_store
85
+ end
86
+
82
87
  return nil unless File.exist?(@ta_path) && !File.empty?(@ta_path)
83
88
 
84
89
  data = JSON.parse(File.read(@ta_path))
@@ -92,6 +97,27 @@ module Arca
92
97
  nil
93
98
  end
94
99
 
100
+ def restore_ta_from_store
101
+ h = @store.read(cuit: @cuit, env: @env, service: @service)
102
+ return nil if h.nil? || !h.is_a?(Hash)
103
+
104
+ {
105
+ token: h[:token],
106
+ sign: h[:sign],
107
+ generation_time: normalize_ta_time(h[:generation_time]),
108
+ expiration_time: normalize_ta_time(h[:expiration_time])
109
+ }
110
+ end
111
+
112
+ def normalize_ta_time(value)
113
+ return nil if value.nil?
114
+ return value if value.is_a?(Time)
115
+
116
+ Time.parse(value.to_s)
117
+ rescue ArgumentError
118
+ nil
119
+ end
120
+
95
121
  def ta_expirado?(ta)
96
122
  if ta.nil?
97
123
  true
@@ -103,6 +129,25 @@ module Arca
103
129
  end
104
130
 
105
131
  def persist_ta(ta)
132
+ if @store
133
+ begin
134
+ @store.write(
135
+ cuit: @cuit,
136
+ env: @env,
137
+ service: @service,
138
+ ta: ta,
139
+ expires_at: ta[:expiration_time]
140
+ )
141
+ rescue StandardError => e
142
+ raise ServerError, e
143
+ end
144
+ return
145
+ end
146
+
147
+ persist_ta_to_file(ta)
148
+ end
149
+
150
+ def persist_ta_to_file(ta)
106
151
  dir = File.dirname(@ta_path)
107
152
  FileUtils.mkdir_p(dir)
108
153
 
@@ -86,5 +86,62 @@ module Arca
86
86
  assert_equal Errno::EACCES, error.cause.class
87
87
  assert_match(/Permission denied/, error.cause.message)
88
88
  end
89
+
90
+ def test_auth_con_store_usa_read_y_no_llama_login_si_ta_valido
91
+ Time.stubs(:now).returns(Time.local(2010, 1, 1))
92
+ ta = { token: "stored-token", sign: "stored-sign", generation_time: Time.now, expiration_time: Time.now + 3600 }
93
+ store = Object.new
94
+ store.expects(:read).with(cuit: "20123456789", env: :test, service: "wsfe").returns(ta)
95
+
96
+ ws = WSAA.new(cuit: "20123456789", env: :test, service: "wsfe", wsdl: Arca::WSFE::WSDL[:test], store: store)
97
+ ws.expects(:login).never
98
+
99
+ assert_equal({ token: "stored-token", sign: "stored-sign" }, ws.auth)
100
+ end
101
+
102
+ def test_auth_con_store_llama_login_y_write_cuando_read_devuelve_nil
103
+ Time.stubs(:now).returns(Time.local(2010, 1, 1))
104
+ ta = { token: "new-token", sign: "new-sign", generation_time: Time.now, expiration_time: Time.now + 60 }
105
+ store = Object.new
106
+ store.expects(:read).with(cuit: "20123456789", env: :test, service: "wsfe").returns(nil)
107
+ store.expects(:write).with(cuit: "20123456789", env: :test, service: "wsfe", ta: ta, expires_at: ta[:expiration_time]).returns(nil)
108
+
109
+ ws = WSAA.new(cuit: "20123456789", env: :test, service: "wsfe", wsdl: Arca::WSFE::WSDL[:test], store: store)
110
+ ws.expects(:login).returns(ta)
111
+
112
+ ws.auth
113
+ end
114
+
115
+ def test_auth_con_store_normaliza_tiempos_iso8601_de_read
116
+ Time.stubs(:now).returns(Time.local(2010, 1, 1))
117
+ gen_str = "2010-01-01T00:00:00-03:00"
118
+ exp_str = "2010-01-01T02:00:00-03:00"
119
+ store = Object.new
120
+ store.expects(:read).with(cuit: "20123456789", env: :test, service: "wsfe").returns({ token: "t", sign: "s", generation_time: gen_str, expiration_time: exp_str })
121
+
122
+ ws = WSAA.new(cuit: "20123456789", env: :test, service: "wsfe", wsdl: Arca::WSFE::WSDL[:test], store: store)
123
+ ws.expects(:login).never
124
+
125
+ ws.auth
126
+ assert_instance_of(Time, ws.ta[:generation_time])
127
+ assert_instance_of(Time, ws.ta[:expiration_time])
128
+ assert_equal(Time.parse(gen_str), ws.ta[:generation_time])
129
+ assert_equal(Time.parse(exp_str), ws.ta[:expiration_time])
130
+ end
131
+
132
+ def test_persist_ta_con_store_encapsula_errores_en_server_error
133
+ ta = { token: "t", sign: "s", generation_time: Time.now, expiration_time: Time.now + 60 }
134
+ store = Class.new do
135
+ def read(*) nil end
136
+ def write(*) raise Errno::EACCES, "Permission denied" end
137
+ end.new
138
+
139
+ ws = WSAA.new(cuit: "1", env: :test, service: "wsfe", wsdl: Arca::WSFE::WSDL[:test], store: store)
140
+ assert_same store, ws.store
141
+
142
+ error = assert_raises(ServerError) { ws.send(:persist_ta, ta) }
143
+ assert_equal Errno::EACCES, error.cause.class
144
+ assert_match(/Permission denied/, error.cause.message)
145
+ end
89
146
  end
90
147
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: arca.rb
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.2
4
+ version: 1.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Arca Kit