europe 0.0.27 → 0.0.29

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: d0918fb1141de821b11aff7fb2d11da6ef7c75b2870e3b90a1373cfd26dc5fbe
4
- data.tar.gz: f7b250f54690746d5339f6209625238461b7dd7edcc4d58adad5e095660186be
3
+ metadata.gz: 296e93d88ac5b6bf8a62eb5247a2bdd06b5c6d6062d76f2cc8c334022b7d5d61
4
+ data.tar.gz: a47e82a08f0a4c62d515dab2cb3ad6fc522cb425d99a7ceed13f890671f28533
5
5
  SHA512:
6
- metadata.gz: f57c86ea54e7539887b3f8674035bca535379c5fcc1b532e4a995f0d67c3435f34e5195487db2ecabbbe6c3a0d1846ae70b232ce18ab9d2436dadd2bb7ef6864
7
- data.tar.gz: d05a4f28813638f846d0e507c60e7f4f731d843914448c85b68ad3b4011f61784bb8dd15a501fd5bfdbd2c21c79ba7aa2692f47bece6701444dbf09bb77276a3
6
+ metadata.gz: f4c3d6bf252d2c593e86d640889f03d198928e31cb5fe9a70bdb45a69962a6bc87818b15c4436230b1d55451344a1373f19cdc214cb60e87ca8b9cb2b468f2c5
7
+ data.tar.gz: 8e4b235296425351f3a617a768356ea04f26cb566a1e22dba4153df080cb07733006aa03521139d8e1314ed71a59509e72350c020c783070af7c5452ab7dfe0f
data/.rubocop.yml CHANGED
@@ -19,3 +19,15 @@ Style/CaseLikeIf:
19
19
 
20
20
  Naming/PredicateMethod:
21
21
  Enabled: false
22
+
23
+ Metrics/ModuleLength:
24
+ Max: 150
25
+ CountAsOne: ['hash', 'array']
26
+
27
+ Metrics/ClassLength:
28
+ Exclude:
29
+ - 'test/**/*'
30
+
31
+ Metrics/MethodLength:
32
+ Exclude:
33
+ - 'test/**/*'
data/CHANGELOG.md CHANGED
@@ -1,6 +1,13 @@
1
1
  # Change Log
2
2
  All notable changes to this project will be documented in this file.
3
3
  This project adheres to [Semantic Versioning](http://semver.org/).
4
+ ## 0.0.29
5
+ - Added Croatia to Eurozone (oops!)
6
+ - [Full Changelog](https://github.com/gem-shards/europe.rb/compare/v0.0.28...v0.0.29)
7
+ ## 0.0.28
8
+ - Added support for batch VAT checks
9
+ - Updated fallback rates for Estonia and Romania
10
+ - [Full Changelog](https://github.com/gem-shards/europe.rb/compare/v0.0.27...v0.0.28)
4
11
  ## 0.0.27
5
12
  - Updated VIES VAT check service to their REST API instead of their SOAP API
6
13
  - Improved error handling for the VAT check service
data/README.md CHANGED
@@ -7,6 +7,7 @@ This gem provides EU governmental data, extracted from various EU / EC websites.
7
7
  - [Installation](#installation)
8
8
  - [Usage](#usage)
9
9
  - [Validating VAT numbers](#validating-vat-numbers)
10
+ - [Batch VAT validation](#batch-vat-validation)
10
11
  - [Validate VAT number format](#validate-vat-number-format)
11
12
  - [Validate Postal code format](#validate-postal-code-format)
12
13
  - [Retrieving VAT rates for each EC/EU member](#retrieving-vat-rates-for-each-eceu-member)
@@ -55,6 +56,34 @@ Response
55
56
  :address => nil }
56
57
  ```
57
58
 
59
+ ### Batch VAT validation
60
+ Validate 3-100 VAT numbers in one request using the VIES batch API. The process is asynchronous: submit numbers, poll for status, then download the report.
61
+
62
+ **Step 1: Submit batch**
63
+ ```ruby
64
+ result = Europe::Vat::Batch.validate(['NL009291477B01', 'BE0123456789', 'DE123456789'])
65
+ # => { token: "a1b2c3d4-..." }
66
+ ```
67
+ Optionally pass your own VAT number as requester:
68
+ ```ruby
69
+ result = Europe::Vat::Batch.validate(vat_numbers, requester: 'NL009291477B01')
70
+ ```
71
+
72
+ **Step 2: Check status**
73
+ ```ruby
74
+ Europe::Vat::Batch.status(result[:token])
75
+ # => { token: "a1b2c3d4-...", filename: "batch.csv", status: :completed,
76
+ # percentage: 100, created_at: "2026-06-08...", completed_at: "2026-06-08..." }
77
+ ```
78
+
79
+ **Step 3: Download report**
80
+ ```ruby
81
+ report = Europe::Vat::Batch.result(result[:token])
82
+ # => { data: "...csv content...", content_type: "text/csv", filename: "report.csv" }
83
+ ```
84
+
85
+ All methods return error symbols on failure (`:timeout`, `:service_unavailable`, `:too_few_rows`, `:too_many_rows`, etc.).
86
+
58
87
  ### Validate VAT number format
59
88
  Call
60
89
  ```ruby
@@ -41,7 +41,7 @@ module Europe
41
41
  tld: '.fr', currency: :EUR, capital: 'Paris' },
42
42
  HR: { name: 'Croatia', source_name: 'Hrvatska',
43
43
  official_name: 'Republic of Croatia',
44
- tld: '.hr', currency: :HRK, capital: 'Zagreb' },
44
+ tld: '.hr', currency: :EUR, capital: 'Zagreb' },
45
45
  IT: { name: 'Italy', source_name: 'Italia',
46
46
  official_name: 'Italian Republic',
47
47
  tld: '.it', currency: :EUR, capital: 'Rome' },
@@ -0,0 +1,158 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'uri'
4
+ require 'net/http'
5
+ require 'json'
6
+ require 'securerandom'
7
+
8
+ module Europe
9
+ module Vat
10
+ # Batch VAT validation via VIES REST API
11
+ module Batch
12
+ BATCH_API_URL = 'https://ec.europa.eu/taxation_customs/vies/rest-api/vat-validation'
13
+ BATCH_REPORT_URL = 'https://ec.europa.eu/taxation_customs/vies/rest-api/vat-validation-report'
14
+
15
+ BATCH_ERRORS = {
16
+ 'VOW-ERR-6000' => :invalid_file_extension,
17
+ 'VOW-ERR-6001' => :too_many_rows,
18
+ 'VOW-ERR-6002' => :too_few_rows,
19
+ 'VOW-ERR-6005' => :filter_violation,
20
+ 'VOW-ERR-6006' => :token_not_found,
21
+ 'VOW-ERR-6007' => :invalid_file_structure,
22
+ 'VOW-ERR-6008' => :invalid_file_structure,
23
+ 'VOW-ERR-6011' => :file_too_large
24
+ }.freeze
25
+
26
+ BATCH_MIN_ROWS = 3
27
+ BATCH_MAX_ROWS = 100
28
+
29
+ HEADERS = {
30
+ 'Content-Type' => 'application/json',
31
+ 'Accept' => 'application/json'
32
+ }.freeze
33
+
34
+ class << self
35
+ def validate(vat_numbers, requester: nil)
36
+ return :too_few_rows if vat_numbers.size < BATCH_MIN_ROWS
37
+ return :too_many_rows if vat_numbers.size > BATCH_MAX_ROWS
38
+
39
+ with_error_handling do
40
+ body = parse_json_response(upload(generate_csv(vat_numbers, requester)))
41
+ return body if body.is_a?(Symbol)
42
+
43
+ { token: body['token'] }
44
+ end
45
+ end
46
+
47
+ def status(token)
48
+ with_error_handling do
49
+ body = parse_json_response(http_get("#{BATCH_API_URL}/#{token}", HEADERS))
50
+ return body if body.is_a?(Symbol)
51
+
52
+ {
53
+ token: body['token'], filename: body['filename'],
54
+ status: body['status']&.downcase&.to_sym, percentage: body['percentage'],
55
+ created_at: body['creationDateTime'], completed_at: body['completionTime']
56
+ }
57
+ end
58
+ end
59
+
60
+ def result(token)
61
+ with_error_handling do
62
+ response = http_get("#{BATCH_REPORT_URL}/#{token}")
63
+ return handle_error(response) unless response.is_a?(Net::HTTPSuccess)
64
+
65
+ error = check_json_error(response)
66
+ return error if error
67
+
68
+ { data: response.body, content_type: response['Content-Type'], filename: extract_filename(response) }
69
+ end
70
+ end
71
+
72
+ private
73
+
74
+ def with_error_handling
75
+ yield
76
+ rescue Net::OpenTimeout, Net::ReadTimeout
77
+ :timeout
78
+ rescue Errno::ECONNREFUSED, Errno::ECONNRESET, Errno::ETIMEDOUT, SocketError, OpenSSL::SSL::SSLError
79
+ :service_unavailable
80
+ rescue JSON::ParserError
81
+ :service_error
82
+ end
83
+
84
+ def http_get(url, headers = {})
85
+ uri = URI.parse(url)
86
+ Net::HTTP.start(uri.host, uri.port, use_ssl: true) do |http|
87
+ http.request(Net::HTTP::Get.new(uri.request_uri, headers))
88
+ end
89
+ end
90
+
91
+ def parse_json_response(response)
92
+ return handle_error(response) unless response.is_a?(Net::HTTPSuccess)
93
+
94
+ body = JSON.parse(response.body)
95
+ return handle_error_body(body) if body['actionSucceed'] == false
96
+
97
+ body
98
+ end
99
+
100
+ def check_json_error(response)
101
+ return unless response['Content-Type']&.include?('application/json')
102
+
103
+ body = JSON.parse(response.body)
104
+ handle_error_body(body) if body['actionSucceed'] == false
105
+ end
106
+
107
+ def extract_filename(response)
108
+ disposition = response['Content-Disposition']
109
+ return unless disposition
110
+
111
+ disposition.match(/filename="?(.+?)"?(?:;|$)/)&.captures&.first
112
+ end
113
+
114
+ def handle_error(response)
115
+ body = JSON.parse(response.body)
116
+ handle_error_body(body)
117
+ rescue JSON::ParserError
118
+ :service_error
119
+ end
120
+
121
+ def handle_error_body(body)
122
+ error_code = body.dig('errorWrappers', 0, 'error')
123
+ BATCH_ERRORS[error_code] || Vat::VIES_ERRORS[error_code] || :service_error
124
+ end
125
+
126
+ def generate_csv(vat_numbers, requester)
127
+ requester_cc = requester ? requester[0..1] : ''
128
+ requester_vat = requester ? requester[2..-1] : ''
129
+ rows = ['"MS Code","VAT Number","Requester MS Code","Requester VAT Number"']
130
+ vat_numbers.each do |vat|
131
+ rows << "\"#{vat[0..1]}\",\"#{vat[2..-1]}\",\"#{requester_cc}\",\"#{requester_vat}\""
132
+ end
133
+ "#{rows.join("\n")}\n"
134
+ end
135
+
136
+ def upload(csv_content)
137
+ uri = URI.parse(BATCH_API_URL)
138
+ boundary = "EuropeGem#{SecureRandom.hex(16)}"
139
+ Net::HTTP.start(uri.host, uri.port, use_ssl: true) do |http|
140
+ request = Net::HTTP::Post.new(uri.request_uri)
141
+ request['Content-Type'] = "multipart/form-data; boundary=#{boundary}"
142
+ request['Accept'] = 'application/json'
143
+ request.body = build_multipart_body(csv_content, boundary)
144
+ http.request(request)
145
+ end
146
+ end
147
+
148
+ def build_multipart_body(csv_content, boundary)
149
+ "--#{boundary}\r\n" \
150
+ "Content-Disposition: form-data; name=\"fileToUpload\"; filename=\"batch.csv\"\r\n" \
151
+ "Content-Type: text/csv\r\n\r\n" \
152
+ "#{csv_content}" \
153
+ "\r\n--#{boundary}--\r\n"
154
+ end
155
+ end
156
+ end
157
+ end
158
+ end
@@ -7,10 +7,10 @@ module Europe
7
7
  # Rates
8
8
  module Rates
9
9
  FALLBACK_RATES = {
10
- AT: 20.0, BE: 21.0, BG: 20.0, CY: 19.0, CZ: 21.0, DE: 19.0, DK: 25.0, EE: 22.0,
10
+ AT: 20.0, BE: 21.0, BG: 20.0, CY: 19.0, CZ: 21.0, DE: 19.0, DK: 25.0, EE: 24.0,
11
11
  EL: 24.0, ES: 21.0, FI: 25.5, FR: 20.0, HR: 25.0, HU: 27.0, IE: 23.0,
12
12
  IT: 22.0, LT: 21.0, LU: 17.0, LV: 21.0, MT: 18.0, NL: 21.0, PL: 23.0, PT: 23.0,
13
- RO: 19.0, SE: 25.0, SI: 22.0, SK: 23.0
13
+ RO: 21.0, SE: 25.0, SI: 22.0, SK: 23.0
14
14
  }.freeze
15
15
  RATES_URL = 'https://raw.githubusercontent.com/benbucksch/eu-vat-rates' \
16
16
  '/refs/heads/master/rates.json'
@@ -2,6 +2,7 @@
2
2
 
3
3
  require 'europe/vat/rates'
4
4
  require 'europe/vat/format'
5
+ require 'europe/vat/batch'
5
6
  require 'uri'
6
7
  require 'net/http'
7
8
  require 'json'
@@ -39,6 +40,34 @@ module Europe
39
40
  :service_unavailable
40
41
  end
41
42
 
43
+ def self.validate_with_country_code(country_code, number)
44
+ return :invalid_input if number.size < 4
45
+
46
+ # Remove country code from number if it's present
47
+ number = number[2..-1] if number[0..1].upcase.to_s == country_code.to_s
48
+
49
+ response = send_request(country_code, number)
50
+ return handle_error_response(response) unless response.is_a?(Net::HTTPSuccess)
51
+
52
+ setup_response(response)
53
+ rescue Net::OpenTimeout, Net::ReadTimeout
54
+ :timeout
55
+ rescue Errno::ECONNREFUSED, Errno::ECONNRESET, Errno::ETIMEDOUT, SocketError, OpenSSL::SSL::SSLError
56
+ :service_unavailable
57
+ end
58
+
59
+ def self.batch_validate(vat_numbers, requester: nil)
60
+ Batch.validate(vat_numbers, requester: requester)
61
+ end
62
+
63
+ def self.batch_status(token)
64
+ Batch.status(token)
65
+ end
66
+
67
+ def self.batch_result(token)
68
+ Batch.result(token)
69
+ end
70
+
42
71
  def self.handle_error_response(response)
43
72
  body = JSON.parse(response.body)
44
73
  error_code = body.dig('errorWrappers', 0, 'error')
@@ -2,5 +2,5 @@
2
2
 
3
3
  # Europe version
4
4
  module Europe
5
- VERSION = '0.0.27'
5
+ VERSION = '0.0.29'
6
6
  end
@@ -16,7 +16,7 @@ module Europe
16
16
 
17
17
  reversed_hash =
18
18
  Europe::Countries::Reversed.generate(:currency)
19
- assert_equal reversed_hash[:EUR].count, 19
19
+ assert_equal reversed_hash[:EUR].count, 20
20
20
  end
21
21
 
22
22
  def test_eurozone
@@ -0,0 +1,203 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'test_helper'
4
+
5
+ module Europe
6
+ module Vat
7
+ BATCH_SUCCESS_BODY = '{"token":"test-token-uuid"}'
8
+ BATCH_STRUCTURE_ERROR_BODY = '{"actionSucceed":false,"errorWrappers":' \
9
+ '[{"error":"VOW-ERR-6008","message":' \
10
+ '"Your file does not match the expected structure"}]}'
11
+ BATCH_TOKEN_NOT_FOUND_BODY = '{"actionSucceed":false,"errorWrappers":' \
12
+ '[{"error":"VOW-ERR-6006","message":' \
13
+ '"The token is not valid"}]}'
14
+ BATCH_STATUS_PROCESSING_BODY = '{"token":"test-token-uuid","filename":"batch.csv",' \
15
+ '"creationDateTime":"2026-06-08T09:47:13.720Z",' \
16
+ '"status":"PROCESSING","processingStartTime":' \
17
+ '"2026-06-08T09:47:13.852Z","percentage":66.67}'
18
+ BATCH_STATUS_COMPLETED_BODY = '{"token":"test-token-uuid","filename":"batch.csv",' \
19
+ '"creationDateTime":"2026-06-08T09:47:13.720Z",' \
20
+ '"status":"COMPLETED","processingStartTime":' \
21
+ '"2026-06-08T09:47:13.852Z","completionTime":' \
22
+ '"2026-06-08T09:47:49.937Z","percentage":100.0}'
23
+
24
+ class BatchValidationTest < Minitest::Test
25
+ VAT_NUMBERS = %w[NL009291477B01 DE115235681 FR40303265045].freeze
26
+
27
+ def setup
28
+ WebMock.enable!
29
+ end
30
+
31
+ def teardown
32
+ WebMock.disable!
33
+ end
34
+
35
+ def test_batch_validate_returns_token
36
+ stub_request(:post, Europe::Vat::Batch::BATCH_API_URL)
37
+ .to_return(status: 200, body: BATCH_SUCCESS_BODY,
38
+ headers: { 'Content-Type' => 'application/json' })
39
+
40
+ result = Europe::Vat::Batch.validate(VAT_NUMBERS)
41
+ assert_equal({ token: 'test-token-uuid' }, result)
42
+ end
43
+
44
+ def test_batch_validate_too_few_rows
45
+ assert_equal :too_few_rows, Europe::Vat::Batch.validate(%w[NL009291477B01 DE115235681])
46
+ end
47
+
48
+ def test_batch_validate_too_many_rows
49
+ numbers = (1..101).map { |i| "NL#{i.to_s.rjust(12, '0')}" }
50
+ assert_equal :too_many_rows, Europe::Vat::Batch.validate(numbers)
51
+ end
52
+
53
+ def test_batch_validate_with_requester
54
+ stub_request(:post, Europe::Vat::Batch::BATCH_API_URL)
55
+ .with { |req| req.body.include?('NL') && req.body.include?('009291477B01') }
56
+ .to_return(status: 200, body: BATCH_SUCCESS_BODY,
57
+ headers: { 'Content-Type' => 'application/json' })
58
+
59
+ result = Europe::Vat::Batch.validate(VAT_NUMBERS, requester: 'NL009291477B01')
60
+ assert_equal({ token: 'test-token-uuid' }, result)
61
+ end
62
+
63
+ def test_batch_validate_structure_error
64
+ stub_request(:post, Europe::Vat::Batch::BATCH_API_URL)
65
+ .to_return(status: 400, body: BATCH_STRUCTURE_ERROR_BODY,
66
+ headers: { 'Content-Type' => 'application/json' })
67
+
68
+ assert_equal :invalid_file_structure, Europe::Vat::Batch.validate(VAT_NUMBERS)
69
+ end
70
+
71
+ def test_batch_validate_timeout
72
+ stub_request(:post, Europe::Vat::Batch::BATCH_API_URL).to_timeout
73
+
74
+ assert_equal :timeout, Europe::Vat::Batch.validate(VAT_NUMBERS)
75
+ end
76
+
77
+ def test_batch_validate_connection_refused
78
+ stub_request(:post, Europe::Vat::Batch::BATCH_API_URL).to_raise(Errno::ECONNREFUSED)
79
+
80
+ assert_equal :service_unavailable, Europe::Vat::Batch.validate(VAT_NUMBERS)
81
+ end
82
+
83
+ def test_batch_status_processing
84
+ stub_request(:get, "#{Europe::Vat::Batch::BATCH_API_URL}/test-token-uuid")
85
+ .to_return(status: 200, body: BATCH_STATUS_PROCESSING_BODY,
86
+ headers: { 'Content-Type' => 'application/json' })
87
+
88
+ result = Europe::Vat::Batch.status('test-token-uuid')
89
+ assert_equal :processing, result[:status]
90
+ assert_equal 66.67, result[:percentage]
91
+ end
92
+
93
+ def test_batch_status_completed
94
+ stub_request(:get, "#{Europe::Vat::Batch::BATCH_API_URL}/test-token-uuid")
95
+ .to_return(status: 200, body: BATCH_STATUS_COMPLETED_BODY,
96
+ headers: { 'Content-Type' => 'application/json' })
97
+
98
+ result = Europe::Vat::Batch.status('test-token-uuid')
99
+ assert_equal :completed, result[:status]
100
+ assert_equal 100.0, result[:percentage]
101
+ assert_equal 'test-token-uuid', result[:token]
102
+ refute_nil result[:completed_at]
103
+ end
104
+
105
+ def test_batch_status_invalid_token
106
+ stub_request(:get, "#{Europe::Vat::Batch::BATCH_API_URL}/bad-token")
107
+ .to_return(status: 400, body: BATCH_TOKEN_NOT_FOUND_BODY,
108
+ headers: { 'Content-Type' => 'application/json' })
109
+
110
+ assert_equal :token_not_found, Europe::Vat::Batch.status('bad-token')
111
+ end
112
+
113
+ def test_batch_status_timeout
114
+ stub_request(:get, "#{Europe::Vat::Batch::BATCH_API_URL}/test-token-uuid").to_timeout
115
+
116
+ assert_equal :timeout, Europe::Vat::Batch.status('test-token-uuid')
117
+ end
118
+
119
+ def test_batch_result_returns_data
120
+ stub_request(:get, "#{Europe::Vat::Batch::BATCH_REPORT_URL}/test-token-uuid")
121
+ .to_return(
122
+ status: 200,
123
+ body: 'fake-xlsx-binary-data',
124
+ headers: {
125
+ 'Content-Type' => 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
126
+ 'Content-Disposition' => 'attachment; filename="report.xlsx"'
127
+ }
128
+ )
129
+
130
+ result = Europe::Vat::Batch.result('test-token-uuid')
131
+ assert_equal 'fake-xlsx-binary-data', result[:data]
132
+ assert_equal 'report.xlsx', result[:filename]
133
+ assert result[:content_type].include?('spreadsheetml')
134
+ end
135
+
136
+ def test_batch_result_invalid_token
137
+ stub_request(:get, "#{Europe::Vat::Batch::BATCH_REPORT_URL}/bad-token")
138
+ .to_return(status: 400, body: BATCH_TOKEN_NOT_FOUND_BODY,
139
+ headers: { 'Content-Type' => 'application/json' })
140
+
141
+ assert_equal :token_not_found, Europe::Vat::Batch.result('bad-token')
142
+ end
143
+
144
+ def test_batch_result_timeout
145
+ stub_request(:get, "#{Europe::Vat::Batch::BATCH_REPORT_URL}/test-token-uuid").to_timeout
146
+
147
+ assert_equal :timeout, Europe::Vat::Batch.result('test-token-uuid')
148
+ end
149
+
150
+ def test_generate_batch_csv_format
151
+ csv = Europe::Vat::Batch.send(:generate_csv, VAT_NUMBERS, nil)
152
+ lines = csv.strip.split("\n")
153
+
154
+ assert_equal 4, lines.size
155
+ assert_equal '"MS Code","VAT Number","Requester MS Code","Requester VAT Number"', lines[0]
156
+ assert_equal '"NL","009291477B01","",""', lines[1]
157
+ assert_equal '"DE","115235681","",""', lines[2]
158
+ assert_equal '"FR","40303265045","",""', lines[3]
159
+ end
160
+
161
+ def test_generate_batch_csv_with_requester
162
+ csv = Europe::Vat::Batch.send(:generate_csv, VAT_NUMBERS, 'NL009291477B01')
163
+ lines = csv.strip.split("\n")
164
+
165
+ assert_equal '"NL","009291477B01","NL","009291477B01"', lines[1]
166
+ end
167
+
168
+ # Delegation tests — ensure Europe::Vat.batch_* still works
169
+ def test_delegation_batch_validate
170
+ stub_request(:post, Europe::Vat::Batch::BATCH_API_URL)
171
+ .to_return(status: 200, body: BATCH_SUCCESS_BODY,
172
+ headers: { 'Content-Type' => 'application/json' })
173
+
174
+ result = Europe::Vat.batch_validate(VAT_NUMBERS)
175
+ assert_equal({ token: 'test-token-uuid' }, result)
176
+ end
177
+
178
+ def test_delegation_batch_status
179
+ stub_request(:get, "#{Europe::Vat::Batch::BATCH_API_URL}/test-token-uuid")
180
+ .to_return(status: 200, body: BATCH_STATUS_COMPLETED_BODY,
181
+ headers: { 'Content-Type' => 'application/json' })
182
+
183
+ result = Europe::Vat.batch_status('test-token-uuid')
184
+ assert_equal :completed, result[:status]
185
+ end
186
+
187
+ def test_delegation_batch_result
188
+ stub_request(:get, "#{Europe::Vat::Batch::BATCH_REPORT_URL}/test-token-uuid")
189
+ .to_return(
190
+ status: 200,
191
+ body: 'fake-xlsx-binary-data',
192
+ headers: {
193
+ 'Content-Type' => 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
194
+ 'Content-Disposition' => 'attachment; filename="report.xlsx"'
195
+ }
196
+ )
197
+
198
+ result = Europe::Vat.batch_result('test-token-uuid')
199
+ assert_equal 'fake-xlsx-binary-data', result[:data]
200
+ end
201
+ end
202
+ end
203
+ end
metadata CHANGED
@@ -1,14 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: europe
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.27
4
+ version: 0.0.29
5
5
  platform: ruby
6
6
  authors:
7
7
  - Gem shards
8
- autorequire:
9
8
  bindir: bin
10
9
  cert_chain: []
11
- date: 2026-06-08 00:00:00.000000000 Z
10
+ date: 1980-01-02 00:00:00.000000000 Z
12
11
  dependencies:
13
12
  - !ruby/object:Gem::Dependency
14
13
  name: byebug
@@ -165,6 +164,7 @@ files:
165
164
  - lib/europe/postal/postal.rb
166
165
  - lib/europe/rake_task.rb
167
166
  - lib/europe/tasks/eurostat.rake
167
+ - lib/europe/vat/batch.rb
168
168
  - lib/europe/vat/format.rb
169
169
  - lib/europe/vat/rates.rb
170
170
  - lib/europe/vat/vat.rb
@@ -174,6 +174,7 @@ files:
174
174
  - test/europe/currency/exchange_rates_test.rb
175
175
  - test/europe/eurostat/eurostat_test.rb
176
176
  - test/europe/postal/format_test.rb
177
+ - test/europe/vat/batch_validation_test.rb
177
178
  - test/europe/vat/format_test.rb
178
179
  - test/europe/vat/rates_test.rb
179
180
  - test/europe/vat/validation_test.rb
@@ -184,7 +185,6 @@ licenses:
184
185
  metadata:
185
186
  source_code_uri: https://github.com/gem-shards/europe.rb
186
187
  changelog_uri: https://github.com/gem-shards/europe.rb/blob/master/CHANGELOG.md
187
- post_install_message:
188
188
  rdoc_options: []
189
189
  require_paths:
190
190
  - lib
@@ -199,8 +199,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
199
199
  - !ruby/object:Gem::Version
200
200
  version: '0'
201
201
  requirements: []
202
- rubygems_version: 3.5.9
203
- signing_key:
202
+ rubygems_version: 4.0.15
204
203
  specification_version: 4
205
204
  summary: Europe is a gem for retrieving and validating EU government data.
206
205
  test_files:
@@ -209,6 +208,7 @@ test_files:
209
208
  - test/europe/currency/exchange_rates_test.rb
210
209
  - test/europe/eurostat/eurostat_test.rb
211
210
  - test/europe/postal/format_test.rb
211
+ - test/europe/vat/batch_validation_test.rb
212
212
  - test/europe/vat/format_test.rb
213
213
  - test/europe/vat/rates_test.rb
214
214
  - test/europe/vat/validation_test.rb