epics 3.0.0.rc1 → 3.0.0.rc2
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/CHANGELOG.md +10 -0
- data/README.md +4 -0
- data/lib/epics/client.rb +24 -2
- data/lib/epics/generic_request.rb +7 -2
- data/lib/epics/response.rb +18 -3
- data/lib/epics/version.rb +3 -1
- data/lib/epics.rb +26 -0
- data/spec/response_spec.rb +31 -0
- data/spec/segmented_download_spec.rb +248 -0
- metadata +4 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 2d81d3bda7eef2d4468cba9327d5e72ad7222eec73e76bfe82c609765a212075
|
|
4
|
+
data.tar.gz: b49bcb37a43532a0f9d5074478e22cfa126c4b3e7bf594894f8be27e60aeca37
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: '09ce2c4c8afa08bf91a00112d8a1078b388a76b40276006706f0c821a1aab7924655a9de56032956d1e74975dda1be8438e42d11211fae95b2d4aa4634b7f7f5'
|
|
7
|
+
data.tar.gz: fe3a33f8d19057cbe437189ae98b18e32eafbedde991bb12b1cf0c10a63384a5910f254272f4d9d43f972951d50fa18601bd2b521253603b2f3893e6a81c9e13
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,15 @@
|
|
|
1
1
|
### Unreleased
|
|
2
2
|
|
|
3
|
+
- [FIX] A download spanning several segments returned only its first segment, so zipped
|
|
4
|
+
downloads such as `C52` and `C53` raised `Zip::Error` and plain ones such as `STA` came
|
|
5
|
+
back cut off without an error. The remaining segments are now requested in the transfer
|
|
6
|
+
phase and the order data is decrypted once all segments are joined. A segmented response
|
|
7
|
+
without a readable `NumSegments` raises `Epics::InvalidResponseError`
|
|
8
|
+
- [FIX] Truncated order data was inflated as far as it went and returned. It now raises
|
|
9
|
+
`Epics::InvalidOrderDataError`, with the `Zlib` or `OpenSSL` error as its `cause`, and a
|
|
10
|
+
negative receipt is sent instead of a positive one, so the bank does not consider a
|
|
11
|
+
broken download as delivered
|
|
12
|
+
|
|
3
13
|
### 3.0.0.rc1
|
|
4
14
|
|
|
5
15
|
Major release adding EBICS 3.0 (H005) and EBICS 2.4 (H003) support, alongside a
|
data/README.md
CHANGED
|
@@ -354,6 +354,10 @@ puts e.STA('2014-09-01', '2014-09-11')
|
|
|
354
354
|
^62RATAJA 13B/1083-032 PSZCZOL^63KI
|
|
355
355
|
```
|
|
356
356
|
|
|
357
|
+
If the order data cannot be decrypted or inflated, e.g. because it arrived truncated,
|
|
358
|
+
the download is rejected to the bank with a negative receipt and
|
|
359
|
+
`Epics::InvalidOrderDataError` is raised, with the underlying error as its `cause`.
|
|
360
|
+
|
|
357
361
|
### Uploads
|
|
358
362
|
|
|
359
363
|
- CD1 (Uploads a SEPA Direct Debit document of type COR1)
|
data/lib/epics/client.rb
CHANGED
|
@@ -604,10 +604,32 @@ class Epics::Client
|
|
|
604
604
|
document = order_type.new(self, *args, **options)
|
|
605
605
|
res = post(url, document.to_xml).body
|
|
606
606
|
document.transaction_id = res.transaction_id
|
|
607
|
+
segments = [res, *remaining_segments(document, res)]
|
|
608
|
+
order_data = begin
|
|
609
|
+
res.decrypt_order_data(segments.map(&:order_data_encrypted).join)
|
|
610
|
+
rescue StandardError => e
|
|
611
|
+
reject_download(document) if res.segmented?
|
|
612
|
+
raise Epics::InvalidOrderDataError.new(order_type.name.split('::').last, e)
|
|
613
|
+
end
|
|
614
|
+
post(url, document.to_receipt_xml) if res.segmented?
|
|
615
|
+
|
|
616
|
+
order_data
|
|
617
|
+
end
|
|
607
618
|
|
|
608
|
-
|
|
619
|
+
def remaining_segments(document, res)
|
|
620
|
+
return [] if !res.segmented? || res.last_segment?
|
|
621
|
+
|
|
622
|
+
(2..res.num_segments).map do |segment_number|
|
|
623
|
+
post(url, document.to_transfer_download_xml(segment_number, segment_number == res.num_segments)).body
|
|
624
|
+
end
|
|
625
|
+
end
|
|
609
626
|
|
|
610
|
-
|
|
627
|
+
# The bank answers a negative receipt with an error, and the reason the order data
|
|
628
|
+
# could not be read is the one worth raising.
|
|
629
|
+
def reject_download(document)
|
|
630
|
+
post(url, document.to_receipt_xml(acknowledged: false))
|
|
631
|
+
rescue StandardError
|
|
632
|
+
nil
|
|
611
633
|
end
|
|
612
634
|
|
|
613
635
|
def download_and_unzip(order_type, *args, **options)
|
|
@@ -50,8 +50,13 @@ class Epics::GenericRequest
|
|
|
50
50
|
raise NotImplementedError
|
|
51
51
|
end
|
|
52
52
|
|
|
53
|
-
def
|
|
54
|
-
builder = request_factory.
|
|
53
|
+
def to_transfer_download_xml(segment_number, is_last_segment)
|
|
54
|
+
builder = request_factory.create_transfer_download(transaction_id, segment_number, is_last_segment)
|
|
55
|
+
builder.to_xml
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def to_receipt_xml(acknowledged: true)
|
|
59
|
+
builder = request_factory.create_transfer_receipt(transaction_id, acknowledged ? 0 : 1)
|
|
55
60
|
builder.to_xml
|
|
56
61
|
end
|
|
57
62
|
|
data/lib/epics/response.rb
CHANGED
|
@@ -46,6 +46,12 @@ class Epics::Response
|
|
|
46
46
|
!!doc.at_xpath('//xmlns:header/xmlns:mutable/xmlns:SegmentNumber', xmlns: client.urn_schema)
|
|
47
47
|
end
|
|
48
48
|
|
|
49
|
+
def num_segments
|
|
50
|
+
Integer(doc.xpath('//xmlns:header/xmlns:static/xmlns:NumSegments', xmlns: client.urn_schema).text, 10)
|
|
51
|
+
rescue ArgumentError => e
|
|
52
|
+
raise Epics::InvalidResponseError.new('NumSegments', e)
|
|
53
|
+
end
|
|
54
|
+
|
|
49
55
|
def return_code
|
|
50
56
|
doc.xpath('//xmlns:ReturnCode', xmlns: client.urn_schema).last.content
|
|
51
57
|
rescue NoMethodError
|
|
@@ -93,11 +99,20 @@ class Epics::Response
|
|
|
93
99
|
end
|
|
94
100
|
|
|
95
101
|
def order_data
|
|
96
|
-
order_data_encrypted
|
|
102
|
+
decrypt_order_data(order_data_encrypted)
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
def order_data_encrypted
|
|
106
|
+
Base64.decode64(doc.xpath('//xmlns:OrderData', xmlns: client.urn_schema).first.content)
|
|
107
|
+
end
|
|
97
108
|
|
|
98
|
-
|
|
109
|
+
# Segments cannot be decrypted on their own, so pass all of them joined. Only the first
|
|
110
|
+
# response carries the transaction key.
|
|
111
|
+
def decrypt_order_data(order_data_encrypted)
|
|
112
|
+
decipher = cipher
|
|
113
|
+
data = decipher.update(order_data_encrypted) + decipher.final
|
|
99
114
|
|
|
100
|
-
Zlib::Inflate.
|
|
115
|
+
Zlib::Inflate.inflate(data)
|
|
101
116
|
end
|
|
102
117
|
|
|
103
118
|
def cipher
|
data/lib/epics/version.rb
CHANGED
data/lib/epics.rb
CHANGED
|
@@ -115,6 +115,32 @@ module Epics
|
|
|
115
115
|
end
|
|
116
116
|
end
|
|
117
117
|
|
|
118
|
+
# Raised when the bank's response lacks a value the protocol requires, or carries one
|
|
119
|
+
# that cannot be read.
|
|
120
|
+
class InvalidResponseError < StandardError
|
|
121
|
+
attr_reader :field
|
|
122
|
+
|
|
123
|
+
def initialize(field, original = nil)
|
|
124
|
+
@field = field
|
|
125
|
+
message = "Could not read #{field} from the bank's response"
|
|
126
|
+
message += " (#{original.message})" if original
|
|
127
|
+
super(message)
|
|
128
|
+
end
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
# Raised when the order data of a download cannot be decrypted or inflated, e.g.
|
|
132
|
+
# because it arrived truncated. The bank has been sent a negative receipt by then.
|
|
133
|
+
class InvalidOrderDataError < StandardError
|
|
134
|
+
attr_reader :order_type
|
|
135
|
+
|
|
136
|
+
def initialize(order_type, original = nil)
|
|
137
|
+
@order_type = order_type
|
|
138
|
+
message = "Could not read the order data of the #{order_type} download"
|
|
139
|
+
message += " (#{original.message})" if original
|
|
140
|
+
super(message)
|
|
141
|
+
end
|
|
142
|
+
end
|
|
143
|
+
|
|
118
144
|
class VersionSupportError < StandardError
|
|
119
145
|
attr_reader :version
|
|
120
146
|
|
data/spec/response_spec.rb
CHANGED
|
@@ -89,6 +89,37 @@ RSpec.describe Epics::Response do
|
|
|
89
89
|
end
|
|
90
90
|
end
|
|
91
91
|
|
|
92
|
+
describe '#num_segments' do
|
|
93
|
+
let(:ebics_response) { File.read('spec/fixtures/xml/sta_response_continued.xml') }
|
|
94
|
+
|
|
95
|
+
it 'pulls the number of segments from the response' do
|
|
96
|
+
expect(subject.num_segments).to eq(2)
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
describe 'when the number has leading zeros' do
|
|
100
|
+
let(:ebics_response) do
|
|
101
|
+
File.read('spec/fixtures/xml/sta_response_continued.xml').sub('<NumSegments>2<', '<NumSegments>09<')
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
it 'reads it as a decimal' do
|
|
105
|
+
expect(subject.num_segments).to eq(9)
|
|
106
|
+
end
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
describe 'when the number is missing' do
|
|
110
|
+
let(:ebics_response) do
|
|
111
|
+
File.read('spec/fixtures/xml/sta_response_continued.xml').sub(%r{<NumSegments>2</NumSegments>}, '')
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
it 'raises an error naming the field' do
|
|
115
|
+
expect { subject.num_segments }.to raise_error(Epics::InvalidResponseError, /NumSegments/) do |error|
|
|
116
|
+
expect(error.field).to eq('NumSegments')
|
|
117
|
+
expect(error.cause).to be_a(ArgumentError)
|
|
118
|
+
end
|
|
119
|
+
end
|
|
120
|
+
end
|
|
121
|
+
end
|
|
122
|
+
|
|
92
123
|
describe '#segmented?' do
|
|
93
124
|
describe 'when the response is segemnted' do
|
|
94
125
|
let(:ebics_response) { File.read('spec/fixtures/xml/sta_response.xml') }
|
|
@@ -0,0 +1,248 @@
|
|
|
1
|
+
RSpec.describe 'segmented downloads' do
|
|
2
|
+
versions = [Epics::Keyring::VERSION_24, Epics::Keyring::VERSION_25, Epics::Keyring::VERSION_30]
|
|
3
|
+
|
|
4
|
+
let(:url) { 'https://194.180.18.30/ebicsweb/ebicsweb' }
|
|
5
|
+
let(:version) { Epics::Keyring::VERSION_25 }
|
|
6
|
+
let(:client) do
|
|
7
|
+
Epics::Client.new(File.open(File.join(File.dirname(__FILE__), 'fixtures', 'SIZBN001.key')),
|
|
8
|
+
'secret', url, 'SIZBN001', 'EBIX', 'EBICS', version:)
|
|
9
|
+
end
|
|
10
|
+
let(:ns) { { 'e' => client.urn_schema } }
|
|
11
|
+
let(:transaction_id) { 'ECD6F062AAEDFA77250526A68CBEC549' }
|
|
12
|
+
|
|
13
|
+
describe 'Epics::GenericRequest#to_transfer_download_xml' do
|
|
14
|
+
let(:order) { Epics::C53.new(client, from: Date.new(2026, 9, 24), to: Date.new(2026, 9, 24)) }
|
|
15
|
+
let(:xml) { Nokogiri::XML(order.to_transfer_download_xml(2, false)) }
|
|
16
|
+
|
|
17
|
+
before { order.transaction_id = transaction_id }
|
|
18
|
+
|
|
19
|
+
versions.each do |version|
|
|
20
|
+
context version do
|
|
21
|
+
let(:version) { version }
|
|
22
|
+
|
|
23
|
+
it 'is a valid EBICS document' do
|
|
24
|
+
expect(xml.to_xml).to be_a_valid_ebics_doc(version)
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
it 'requests the segment of the transaction without sending order data' do
|
|
28
|
+
expect(xml.at_xpath('//e:header/e:static/e:TransactionID', ns).text).to eq(transaction_id)
|
|
29
|
+
expect(xml.at_xpath('//e:header/e:mutable/e:TransactionPhase', ns).text).to eq('Transfer')
|
|
30
|
+
expect(xml.at_xpath('//e:header/e:mutable/e:SegmentNumber', ns).text).to eq('2')
|
|
31
|
+
expect(xml.at_xpath('//e:header/e:mutable/e:SegmentNumber', ns)['lastSegment']).to eq('false')
|
|
32
|
+
expect(xml.at_xpath('//e:body', ns).elements).to be_empty
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
it 'marks the last segment' do
|
|
38
|
+
segment_number = Nokogiri::XML(order.to_transfer_download_xml(3, true))
|
|
39
|
+
.at_xpath('//e:header/e:mutable/e:SegmentNumber', ns)
|
|
40
|
+
|
|
41
|
+
expect(segment_number.text).to eq('3')
|
|
42
|
+
expect(segment_number['lastSegment']).to eq('true')
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
describe 'Epics::GenericRequest#to_receipt_xml' do
|
|
47
|
+
let(:order) { Epics::C53.new(client, from: Date.new(2026, 9, 24), to: Date.new(2026, 9, 24)) }
|
|
48
|
+
|
|
49
|
+
before { order.transaction_id = transaction_id }
|
|
50
|
+
|
|
51
|
+
def receipt_code(xml)
|
|
52
|
+
Nokogiri::XML(xml).at_xpath('//e:body/e:TransferReceipt/e:ReceiptCode', ns).text
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
it 'acknowledges the download by default' do
|
|
56
|
+
expect(receipt_code(order.to_receipt_xml)).to eq('0')
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
versions.each do |version|
|
|
60
|
+
context version do
|
|
61
|
+
let(:version) { version }
|
|
62
|
+
|
|
63
|
+
it 'rejects the download when not acknowledged' do
|
|
64
|
+
xml = order.to_receipt_xml(acknowledged: false)
|
|
65
|
+
|
|
66
|
+
expect(xml).to be_a_valid_ebics_doc(version)
|
|
67
|
+
expect(receipt_code(xml)).to eq('1')
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
describe 'Epics::Client#download' do
|
|
74
|
+
def response(phase, order_data: nil, segment_number: nil, num_segments: nil, return_code: '000000')
|
|
75
|
+
<<~XML
|
|
76
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
|
77
|
+
<ebicsResponse xmlns="#{client.urn_schema}" Version="#{version}" Revision="1">
|
|
78
|
+
<header authenticate="true">
|
|
79
|
+
<static>
|
|
80
|
+
<TransactionID>#{transaction_id}</TransactionID>
|
|
81
|
+
#{"<NumSegments>#{num_segments}</NumSegments>" if num_segments}
|
|
82
|
+
</static>
|
|
83
|
+
<mutable>
|
|
84
|
+
<TransactionPhase>#{phase}</TransactionPhase>
|
|
85
|
+
#{segment_number_xml(segment_number) if segment_number}
|
|
86
|
+
<ReturnCode>#{return_code}</ReturnCode>
|
|
87
|
+
</mutable>
|
|
88
|
+
</header>
|
|
89
|
+
<body>
|
|
90
|
+
#{data_transfer_xml(phase, order_data) if order_data}
|
|
91
|
+
<ReturnCode authenticate="true">000000</ReturnCode>
|
|
92
|
+
</body>
|
|
93
|
+
</ebicsResponse>
|
|
94
|
+
XML
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
def segment_number_xml(segment_number)
|
|
98
|
+
%(<SegmentNumber lastSegment="#{segment_number == segments.size}">#{segment_number}</SegmentNumber>)
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
def data_transfer_xml(phase, order_data)
|
|
102
|
+
<<~XML
|
|
103
|
+
<DataTransfer>
|
|
104
|
+
#{data_encryption_info_xml if phase == 'Initialisation'}
|
|
105
|
+
<OrderData>#{Base64.strict_encode64(order_data)}</OrderData>
|
|
106
|
+
</DataTransfer>
|
|
107
|
+
XML
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
def data_encryption_info_xml
|
|
111
|
+
<<~XML
|
|
112
|
+
<DataEncryptionInfo authenticate="true">
|
|
113
|
+
<TransactionKey>#{Base64.strict_encode64(client.encryption_key.key.public_encrypt(transaction_key))}</TransactionKey>
|
|
114
|
+
</DataEncryptionInfo>
|
|
115
|
+
XML
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
def summarize(request)
|
|
119
|
+
segment_number = request.at_xpath('//e:header/e:mutable/e:SegmentNumber', ns)
|
|
120
|
+
receipt_code = request.at_xpath('//e:body/e:TransferReceipt/e:ReceiptCode', ns)
|
|
121
|
+
|
|
122
|
+
[request.at_xpath('//e:header/e:mutable/e:TransactionPhase', ns).text,
|
|
123
|
+
segment_number&.text,
|
|
124
|
+
segment_number&.[]('lastSegment'),
|
|
125
|
+
receipt_code&.text].compact
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
let(:zipped_order_data) { File.binread(File.join(File.dirname(__FILE__), 'fixtures', 'test.zip')) }
|
|
129
|
+
let(:transaction_key) { SecureRandom.random_bytes(16) }
|
|
130
|
+
let(:order_data_encrypted) do
|
|
131
|
+
cipher = OpenSSL::Cipher.new('aes-128-cbc').encrypt
|
|
132
|
+
cipher.key = transaction_key
|
|
133
|
+
|
|
134
|
+
cipher.update(Zlib::Deflate.deflate(zipped_order_data)) + cipher.final
|
|
135
|
+
end
|
|
136
|
+
let(:missing_bytes) { 0 }
|
|
137
|
+
let(:segment_count) { 3 }
|
|
138
|
+
let(:segments) do
|
|
139
|
+
received = order_data_encrypted.byteslice(0, order_data_encrypted.bytesize - missing_bytes)
|
|
140
|
+
|
|
141
|
+
received.bytes.each_slice((received.bytesize / segment_count.to_f).ceil).map { |bytes| bytes.pack('C*') }
|
|
142
|
+
end
|
|
143
|
+
let(:initialisation_response) do
|
|
144
|
+
response('Initialisation', order_data: segments.first, segment_number: 1, num_segments: segments.size)
|
|
145
|
+
end
|
|
146
|
+
let(:aborted_segment) { nil }
|
|
147
|
+
let(:requests) { [] }
|
|
148
|
+
|
|
149
|
+
subject { client.C53(Date.new(2026, 9, 24), Date.new(2026, 9, 24)) }
|
|
150
|
+
|
|
151
|
+
before do
|
|
152
|
+
stub_request(:post, url).to_return do |http_request|
|
|
153
|
+
requests << summarize(Nokogiri::XML(http_request.body))
|
|
154
|
+
phase, *details = requests.last
|
|
155
|
+
body = case phase
|
|
156
|
+
when 'Initialisation' then initialisation_response
|
|
157
|
+
when 'Transfer'
|
|
158
|
+
segment_number = Integer(details.first)
|
|
159
|
+
if segment_number == aborted_segment
|
|
160
|
+
response('Transfer', return_code: '091102')
|
|
161
|
+
else
|
|
162
|
+
response('Transfer', order_data: segments[segment_number - 1], segment_number:)
|
|
163
|
+
end
|
|
164
|
+
when 'Receipt' then response('Receipt', return_code: details == ['0'] ? '011000' : '011001')
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
{ status: 200, body: }
|
|
168
|
+
end
|
|
169
|
+
end
|
|
170
|
+
|
|
171
|
+
context 'when the order data arrives short by whole cipher blocks' do
|
|
172
|
+
let(:missing_bytes) { 32 }
|
|
173
|
+
|
|
174
|
+
it 'raises after sending a negative receipt' do
|
|
175
|
+
expect { subject }.to raise_error(Epics::InvalidOrderDataError, /C53/) do |error|
|
|
176
|
+
expect(error.cause).to be_a(Zlib::BufError)
|
|
177
|
+
end
|
|
178
|
+
expect(requests).to eq([%w[Initialisation], %w[Transfer 2 false], %w[Transfer 3 true], %w[Receipt 1]])
|
|
179
|
+
end
|
|
180
|
+
end
|
|
181
|
+
|
|
182
|
+
context 'when the order data arrives short by part of a cipher block' do
|
|
183
|
+
let(:missing_bytes) { 5 }
|
|
184
|
+
|
|
185
|
+
it 'raises after sending a negative receipt' do
|
|
186
|
+
expect { subject }.to raise_error(Epics::InvalidOrderDataError, /C53/) do |error|
|
|
187
|
+
expect(error.cause).to be_a(OpenSSL::Cipher::CipherError)
|
|
188
|
+
end
|
|
189
|
+
expect(requests).to eq([%w[Initialisation], %w[Transfer 2 false], %w[Transfer 3 true], %w[Receipt 1]])
|
|
190
|
+
end
|
|
191
|
+
end
|
|
192
|
+
|
|
193
|
+
context 'when the bank aborts the transaction during the transfer' do
|
|
194
|
+
let(:aborted_segment) { 2 }
|
|
195
|
+
|
|
196
|
+
it "raises the bank's error without sending a receipt" do
|
|
197
|
+
expect { subject }.to raise_error(Epics::Error::TechnicalError) do |error|
|
|
198
|
+
expect(error.symbol).to eq('EBICS_TX_ABORT')
|
|
199
|
+
end
|
|
200
|
+
expect(requests).to eq([%w[Initialisation], %w[Transfer 2 false]])
|
|
201
|
+
end
|
|
202
|
+
end
|
|
203
|
+
|
|
204
|
+
context 'when the negative receipt cannot be sent' do
|
|
205
|
+
let(:missing_bytes) { 32 }
|
|
206
|
+
|
|
207
|
+
before do
|
|
208
|
+
stub_request(:post, url).with { |request| summarize(Nokogiri::XML(request.body)) == %w[Receipt 1] }
|
|
209
|
+
.to_raise(Faraday::ConnectionFailed)
|
|
210
|
+
end
|
|
211
|
+
|
|
212
|
+
it 'raises the reason the order data could not be read' do
|
|
213
|
+
expect { subject }.to raise_error(Epics::InvalidOrderDataError, /C53/) do |error|
|
|
214
|
+
expect(error.cause).to be_a(Zlib::BufError)
|
|
215
|
+
end
|
|
216
|
+
end
|
|
217
|
+
end
|
|
218
|
+
|
|
219
|
+
context 'when the response is not segmented' do
|
|
220
|
+
let(:initialisation_response) { response('Initialisation', order_data: order_data_encrypted) }
|
|
221
|
+
|
|
222
|
+
it 'returns the order data without sending a receipt' do
|
|
223
|
+
expect(subject).to eq(["ebics is great\n"])
|
|
224
|
+
expect(requests).to eq([%w[Initialisation]])
|
|
225
|
+
end
|
|
226
|
+
end
|
|
227
|
+
|
|
228
|
+
context 'when the order data fits into a single segment' do
|
|
229
|
+
let(:segment_count) { 1 }
|
|
230
|
+
|
|
231
|
+
it 'returns the order data and sends the receipt' do
|
|
232
|
+
expect(subject).to eq(["ebics is great\n"])
|
|
233
|
+
expect(requests).to eq([%w[Initialisation], %w[Receipt 0]])
|
|
234
|
+
end
|
|
235
|
+
end
|
|
236
|
+
|
|
237
|
+
versions.each do |version|
|
|
238
|
+
context "when the order data spans several segments (#{version})" do
|
|
239
|
+
let(:version) { version }
|
|
240
|
+
|
|
241
|
+
it 'returns the order data of all segments and sends the receipt after the last one' do
|
|
242
|
+
expect(subject).to eq(["ebics is great\n"])
|
|
243
|
+
expect(requests).to eq([%w[Initialisation], %w[Transfer 2 false], %w[Transfer 3 true], %w[Receipt 0]])
|
|
244
|
+
end
|
|
245
|
+
end
|
|
246
|
+
end
|
|
247
|
+
end
|
|
248
|
+
end
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: epics
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 3.0.0.
|
|
4
|
+
version: 3.0.0.rc2
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Lars Brillert
|
|
@@ -455,6 +455,7 @@ files:
|
|
|
455
455
|
- spec/response_business_error_spec.rb
|
|
456
456
|
- spec/response_spec.rb
|
|
457
457
|
- spec/response_validation_spec.rb
|
|
458
|
+
- spec/segmented_download_spec.rb
|
|
458
459
|
- spec/signature_algorithm/rsa_pss_spec.rb
|
|
459
460
|
- spec/spec_helper.rb
|
|
460
461
|
- spec/support/ebics_matcher.rb
|
|
@@ -520,7 +521,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
520
521
|
- !ruby/object:Gem::Version
|
|
521
522
|
version: '0'
|
|
522
523
|
requirements: []
|
|
523
|
-
rubygems_version: 4.0.
|
|
524
|
+
rubygems_version: 4.0.20
|
|
524
525
|
specification_version: 4
|
|
525
526
|
summary: a ruby implementation of the EBICS protocol
|
|
526
527
|
test_files:
|
|
@@ -633,6 +634,7 @@ test_files:
|
|
|
633
634
|
- spec/response_business_error_spec.rb
|
|
634
635
|
- spec/response_spec.rb
|
|
635
636
|
- spec/response_validation_spec.rb
|
|
637
|
+
- spec/segmented_download_spec.rb
|
|
636
638
|
- spec/signature_algorithm/rsa_pss_spec.rb
|
|
637
639
|
- spec/spec_helper.rb
|
|
638
640
|
- spec/support/ebics_matcher.rb
|