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 +4 -4
- data/.rubocop.yml +12 -0
- data/CHANGELOG.md +7 -0
- data/README.md +29 -0
- data/lib/europe/countries/countries.rb +1 -1
- data/lib/europe/vat/batch.rb +158 -0
- data/lib/europe/vat/rates.rb +2 -2
- data/lib/europe/vat/vat.rb +29 -0
- data/lib/europe/version.rb +1 -1
- data/test/europe/countries/country_test.rb +1 -1
- data/test/europe/vat/batch_validation_test.rb +203 -0
- metadata +6 -6
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 296e93d88ac5b6bf8a62eb5247a2bdd06b5c6d6062d76f2cc8c334022b7d5d61
|
|
4
|
+
data.tar.gz: a47e82a08f0a4c62d515dab2cb3ad6fc522cb425d99a7ceed13f890671f28533
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
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: :
|
|
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
|
data/lib/europe/vat/rates.rb
CHANGED
|
@@ -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:
|
|
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:
|
|
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'
|
data/lib/europe/vat/vat.rb
CHANGED
|
@@ -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')
|
data/lib/europe/version.rb
CHANGED
|
@@ -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.
|
|
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:
|
|
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:
|
|
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
|