epics 2.10.0 → 3.0.0.rc1

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.
Files changed (245) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +2 -1
  3. data/.semaphore/semaphore.yml +17 -8
  4. data/CHANGELOG.md +97 -0
  5. data/EBICS_REFERENCES.md +169 -0
  6. data/README.md +219 -50
  7. data/lib/epics/azv.rb +3 -10
  8. data/lib/epics/b2b.rb +3 -10
  9. data/lib/epics/bka.rb +3 -14
  10. data/lib/epics/builders/body_builder/base.rb +26 -0
  11. data/lib/epics/builders/body_builder/v2.rb +9 -0
  12. data/lib/epics/builders/body_builder/v3.rb +9 -0
  13. data/lib/epics/builders/body_builder.rb +6 -0
  14. data/lib/epics/builders/data_encryption_info_builder.rb +29 -0
  15. data/lib/epics/builders/data_transfer_builder/base.rb +52 -0
  16. data/lib/epics/builders/data_transfer_builder/v2.rb +9 -0
  17. data/lib/epics/builders/data_transfer_builder/v3.rb +12 -0
  18. data/lib/epics/builders/data_transfer_builder.rb +6 -0
  19. data/lib/epics/builders/header_builder/base.rb +26 -0
  20. data/lib/epics/builders/header_builder/v2.rb +9 -0
  21. data/lib/epics/builders/header_builder/v3.rb +9 -0
  22. data/lib/epics/builders/header_builder.rb +6 -0
  23. data/lib/epics/builders/mutable_builder.rb +28 -0
  24. data/lib/epics/builders/order_details_builder/base.rb +100 -0
  25. data/lib/epics/builders/order_details_builder/v2.rb +21 -0
  26. data/lib/epics/builders/order_details_builder/v3.rb +51 -0
  27. data/lib/epics/builders/order_details_builder.rb +10 -0
  28. data/lib/epics/builders/request_builder.rb +25 -0
  29. data/lib/epics/builders/static_builder/base.rb +75 -0
  30. data/lib/epics/builders/static_builder/v2.rb +9 -0
  31. data/lib/epics/builders/static_builder/v3.rb +9 -0
  32. data/lib/epics/builders/static_builder.rb +9 -0
  33. data/lib/epics/builders/transfer_receipt_builder.rb +19 -0
  34. data/lib/epics/builders/xml_builder/base.rb +92 -0
  35. data/lib/epics/builders/xml_builder/v24.rb +31 -0
  36. data/lib/epics/builders/xml_builder/v25.rb +31 -0
  37. data/lib/epics/builders/xml_builder/v3.rb +31 -0
  38. data/lib/epics/builders/xml_builder.rb +7 -0
  39. data/lib/epics/builders.rb +13 -0
  40. data/lib/epics/c2s.rb +3 -10
  41. data/lib/epics/c52.rb +2 -14
  42. data/lib/epics/c53.rb +2 -14
  43. data/lib/epics/c54.rb +2 -14
  44. data/lib/epics/c5n.rb +3 -14
  45. data/lib/epics/ccs.rb +3 -10
  46. data/lib/epics/cct.rb +3 -10
  47. data/lib/epics/cd1.rb +3 -10
  48. data/lib/epics/cdb.rb +3 -10
  49. data/lib/epics/cdd.rb +3 -10
  50. data/lib/epics/cds.rb +3 -10
  51. data/lib/epics/cdz.rb +3 -14
  52. data/lib/epics/cip.rb +3 -10
  53. data/lib/epics/client.rb +491 -123
  54. data/lib/epics/crypt/aes.rb +56 -0
  55. data/lib/epics/crypt/x_509.rb +52 -0
  56. data/lib/epics/crypt.rb +5 -0
  57. data/lib/epics/crz.rb +3 -14
  58. data/lib/epics/factories/crypt/aes_factory.rb +5 -0
  59. data/lib/epics/factories/crypt.rb +4 -0
  60. data/lib/epics/factories/request_factory/base.rb +236 -0
  61. data/lib/epics/factories/request_factory/v2.rb +194 -0
  62. data/lib/epics/factories/request_factory/v24.rb +32 -0
  63. data/lib/epics/factories/request_factory/v25.rb +31 -0
  64. data/lib/epics/factories/request_factory/v3.rb +305 -0
  65. data/lib/epics/factories/request_factory.rb +8 -0
  66. data/lib/epics/factories.rb +5 -0
  67. data/lib/epics/fdl.rb +3 -18
  68. data/lib/epics/ful.rb +6 -0
  69. data/lib/epics/generic_request.rb +31 -92
  70. data/lib/epics/generic_upload_request.rb +14 -67
  71. data/lib/epics/haa.rb +3 -9
  72. data/lib/epics/hac.rb +3 -14
  73. data/lib/epics/handlers/auth_signature_handler.rb +102 -0
  74. data/lib/epics/handlers/order_data_handler/base.rb +98 -0
  75. data/lib/epics/handlers/order_data_handler/v2.rb +39 -0
  76. data/lib/epics/handlers/order_data_handler/v24.rb +11 -0
  77. data/lib/epics/handlers/order_data_handler/v25.rb +11 -0
  78. data/lib/epics/handlers/order_data_handler/v3.rb +41 -0
  79. data/lib/epics/handlers/order_data_handler.rb +8 -0
  80. data/lib/epics/handlers/user_signature_handler/base.rb +10 -0
  81. data/lib/epics/handlers/user_signature_handler/v2.rb +17 -0
  82. data/lib/epics/handlers/user_signature_handler/v3.rb +17 -0
  83. data/lib/epics/handlers/user_signature_handler.rb +6 -0
  84. data/lib/epics/handlers.rb +6 -0
  85. data/lib/epics/hev.rb +2 -15
  86. data/lib/epics/hia.rb +4 -55
  87. data/lib/epics/hkd.rb +3 -9
  88. data/lib/epics/hpb.rb +3 -13
  89. data/lib/epics/hpd.rb +3 -9
  90. data/lib/epics/htd.rb +3 -9
  91. data/lib/epics/ini.rb +4 -46
  92. data/lib/epics/keyring.rb +17 -0
  93. data/lib/epics/letter_renderer.rb +17 -22
  94. data/lib/epics/ptk.rb +3 -14
  95. data/lib/epics/response.rb +34 -29
  96. data/lib/epics/services/crypt_service.rb +62 -0
  97. data/lib/epics/services/digest_resolver/base.rb +30 -0
  98. data/lib/epics/services/digest_resolver/v2.rb +13 -0
  99. data/lib/epics/services/digest_resolver/v3.rb +19 -0
  100. data/lib/epics/services/digest_resolver.rb +6 -0
  101. data/lib/epics/services.rb +5 -0
  102. data/lib/epics/signature.rb +50 -0
  103. data/lib/epics/{key.rb → signature_algorithm/base.rb} +14 -21
  104. data/lib/epics/signature_algorithm/rsa.rb +50 -0
  105. data/lib/epics/signature_algorithm/rsapkcs1.rb +16 -0
  106. data/lib/epics/signature_algorithm/rsapss.rb +20 -0
  107. data/lib/epics/signature_algorithm.rb +2 -0
  108. data/lib/epics/sta.rb +3 -14
  109. data/lib/epics/version.rb +1 -1
  110. data/lib/epics/vmk.rb +3 -14
  111. data/lib/epics/wss.rb +3 -9
  112. data/lib/epics/xct.rb +3 -10
  113. data/lib/epics/xds.rb +3 -10
  114. data/lib/epics/xe2.rb +3 -10
  115. data/lib/epics/xe3.rb +3 -10
  116. data/lib/epics/xek.rb +5 -0
  117. data/lib/epics/yct.rb +6 -0
  118. data/lib/epics/z01.rb +2 -14
  119. data/lib/epics/z52.rb +2 -14
  120. data/lib/epics/z53.rb +2 -14
  121. data/lib/epics/z54.rb +2 -14
  122. data/lib/epics/zsr.rb +5 -0
  123. data/lib/epics.rb +69 -5
  124. data/lib/letter/ini.erb +27 -6
  125. data/spec/aes_spec.rb +46 -0
  126. data/spec/auth_signature_handler_spec.rb +58 -0
  127. data/spec/builders_spec.rb +616 -0
  128. data/spec/certificate_handling_spec.rb +189 -0
  129. data/spec/client_setup_block_spec.rb +41 -0
  130. data/spec/client_spec.rb +143 -91
  131. data/spec/crypt_service_spec.rb +132 -0
  132. data/spec/date_range_spec.rb +46 -0
  133. data/spec/digest_resolver_spec.rb +164 -0
  134. data/spec/fixtures/bank_x509.pem +3 -0
  135. data/spec/fixtures/xml/haa.xml +40 -1
  136. data/spec/fixtures/xml/haa_receipt.xml +33 -30
  137. data/spec/fixtures/xml/haa_response_h004.xml +4 -0
  138. data/spec/fixtures/xml/haa_response_h005.xml +38 -0
  139. data/spec/fixtures/xml/hia_request_order_data.xml +24 -1
  140. data/spec/fixtures/xml/hpb_request.xml +1 -1
  141. data/spec/fixtures/xml/hpb_response_x509.xml +16 -0
  142. data/spec/fixtures/xml/htd_order_data_h005.xml +55 -0
  143. data/spec/fixtures/xml/jruby/hia.xml +20 -22
  144. data/spec/fixtures/xml/jruby/ini.xml +1 -3
  145. data/spec/fixtures/xml/ruby/hia.xml +21 -1
  146. data/spec/fixtures/xml/ruby/ini.xml +21 -1
  147. data/spec/handlers_spec.rb +267 -0
  148. data/spec/hpb_malformed_certificate_spec.rb +36 -0
  149. data/spec/key_file_decryption_spec.rb +27 -0
  150. data/spec/key_file_preservation_spec.rb +78 -0
  151. data/spec/keyring_spec.rb +349 -0
  152. data/spec/middleware/parse_ebics_spec.rb +2 -1
  153. data/spec/order_id_spec.rb +65 -0
  154. data/spec/orders/azv_spec.rb +63 -7
  155. data/spec/orders/b2b_spec.rb +68 -3
  156. data/spec/orders/bka_spec.rb +34 -4
  157. data/spec/orders/c2s_spec.rb +62 -9
  158. data/spec/orders/c52_spec.rb +34 -4
  159. data/spec/orders/c53_spec.rb +34 -4
  160. data/spec/orders/c54_spec.rb +36 -4
  161. data/spec/orders/c5n_spec.rb +36 -4
  162. data/spec/orders/camt_parity_spec.rb +80 -0
  163. data/spec/orders/ccs_spec.rb +62 -9
  164. data/spec/orders/cct_spec.rb +83 -9
  165. data/spec/orders/cd1_spec.rb +50 -7
  166. data/spec/orders/cdb_spec.rb +64 -7
  167. data/spec/orders/cdd_spec.rb +64 -7
  168. data/spec/orders/cds_spec.rb +74 -0
  169. data/spec/orders/cdz_spec.rb +67 -4
  170. data/spec/orders/cip_spec.rb +61 -9
  171. data/spec/orders/crz_spec.rb +67 -4
  172. data/spec/orders/fdl_spec.rb +143 -13
  173. data/spec/orders/ful_spec.rb +63 -0
  174. data/spec/orders/haa_spec.rb +115 -9
  175. data/spec/orders/hac_spec.rb +89 -10
  176. data/spec/orders/hev_spec.rb +1 -6
  177. data/spec/orders/hia_spec.rb +25 -22
  178. data/spec/orders/hkd_spec.rb +23 -4
  179. data/spec/orders/hpb_spec.rb +7 -4
  180. data/spec/orders/hpd_spec.rb +23 -4
  181. data/spec/orders/htd_spec.rb +23 -4
  182. data/spec/orders/ini_spec.rb +23 -18
  183. data/spec/orders/ptk_spec.rb +27 -4
  184. data/spec/orders/sta_spec.rb +129 -11
  185. data/spec/orders/vmk_spec.rb +36 -4
  186. data/spec/orders/wss_spec.rb +29 -3
  187. data/spec/orders/x_509_certificate_spec.rb +9 -1
  188. data/spec/orders/xct_spec.rb +63 -0
  189. data/spec/orders/xds_spec.rb +13 -6
  190. data/spec/orders/xe2_spec.rb +64 -7
  191. data/spec/orders/xe3_spec.rb +64 -7
  192. data/spec/orders/xek_spec.rb +31 -0
  193. data/spec/orders/yct_spec.rb +43 -0
  194. data/spec/orders/z01_spec.rb +34 -4
  195. data/spec/orders/z52_spec.rb +34 -4
  196. data/spec/orders/z53_spec.rb +34 -4
  197. data/spec/orders/z54_spec.rb +36 -4
  198. data/spec/orders/zsr_spec.rb +31 -0
  199. data/spec/request_factory_spec.rb +444 -0
  200. data/spec/response_business_error_spec.rb +45 -0
  201. data/spec/response_validation_spec.rb +23 -0
  202. data/spec/{key_spec.rb → signature_algorithm/rsa_pss_spec.rb} +4 -13
  203. data/spec/support/ebics_matcher.rb +17 -9
  204. data/spec/support/ebics_shared_examples.rb +642 -0
  205. data/spec/support/h005_client_helper.rb +23 -0
  206. data/spec/upload_digest_spec.rb +69 -0
  207. data/spec/user_signature_handler_spec.rb +25 -0
  208. data/spec/version_support_error_spec.rb +76 -0
  209. data/spec/xsd/{ebics_hev.xsd → H000/ebics_hev.xsd} +0 -0
  210. data/spec/xsd/H003/ebics.xsd +11 -0
  211. data/spec/xsd/H003/ebics_keymgmt_request.xsd +555 -0
  212. data/spec/xsd/H003/ebics_keymgmt_response.xsd +131 -0
  213. data/spec/xsd/H003/ebics_orders.xsd +1811 -0
  214. data/spec/xsd/H003/ebics_request.xsd +354 -0
  215. data/spec/xsd/H003/ebics_response.xsd +161 -0
  216. data/spec/xsd/{ebics_signature.xsd → H003/ebics_signature.xsd} +0 -0
  217. data/spec/xsd/H003/ebics_types.xsd +2361 -0
  218. data/spec/xsd/H003/xmldsig-core-schema.xsd +318 -0
  219. data/spec/xsd/H004/ebics_signature.xsd +217 -0
  220. data/spec/xsd/H005/ebics_H005.xsd +11 -0
  221. data/spec/xsd/H005/ebics_keymgmt_request_H005.xsd +523 -0
  222. data/spec/xsd/H005/ebics_keymgmt_response_H005.xsd +137 -0
  223. data/spec/xsd/H005/ebics_orders_H005.xsd +2094 -0
  224. data/spec/xsd/H005/ebics_request_H005.xsd +349 -0
  225. data/spec/xsd/H005/ebics_response_H005.xsd +167 -0
  226. data/spec/xsd/H005/ebics_signature_S002.xsd +177 -0
  227. data/spec/xsd/H005/ebics_types_H005.xsd +1885 -0
  228. data/spec/xsd/H005/xmldsig-core-schema.xsd +318 -0
  229. metadata +200 -36
  230. data/lib/epics/header_request.rb +0 -58
  231. data/lib/epics/middleware/xmlsig.rb +0 -16
  232. data/lib/epics/signer.rb +0 -40
  233. data/lib/epics/x_509_certificate.rb +0 -15
  234. data/lib/letter/ini_with_certs.erb +0 -335
  235. data/spec/generic_upload_spec.rb +0 -58
  236. data/spec/hpb_spec.rb +0 -15
  237. data/spec/signer_spec.rb +0 -34
  238. /data/spec/xsd/{ebics_H004.xsd → H004/ebics_H004.xsd} +0 -0
  239. /data/spec/xsd/{ebics_keymgmt_request_H004.xsd → H004/ebics_keymgmt_request_H004.xsd} +0 -0
  240. /data/spec/xsd/{ebics_keymgmt_response_H004.xsd → H004/ebics_keymgmt_response_H004.xsd} +0 -0
  241. /data/spec/xsd/{ebics_orders_H004.xsd → H004/ebics_orders_H004.xsd} +0 -0
  242. /data/spec/xsd/{ebics_request_H004.xsd → H004/ebics_request_H004.xsd} +0 -0
  243. /data/spec/xsd/{ebics_response_H004.xsd → H004/ebics_response_H004.xsd} +0 -0
  244. /data/spec/xsd/{ebics_types_H004.xsd → H004/ebics_types_H004.xsd} +0 -0
  245. /data/spec/xsd/{xmldsig-core-schema.xsd → H004/xmldsig-core-schema.xsd} +0 -0
@@ -0,0 +1,444 @@
1
+ RSpec.describe 'RequestFactory' do
2
+ let(:client) { Epics::Client.new(File.open(File.join(File.dirname(__FILE__), 'fixtures', 'SIZBN001.key')), 'secret', 'https://194.180.18.30/ebicsweb/ebicsweb', 'SIZBN001', 'EBIX', 'EBICS', version:) }
3
+ let(:start_date) { Date.new(2024, 1, 1) }
4
+ let(:end_date) { Date.new(2024, 1, 31) }
5
+ let(:digest) { OpenSSL::Digest::SHA256.digest('test document') }
6
+ let(:transaction_key) { OpenSSL::Cipher::AES.new(128, :CBC).random_key }
7
+ let(:transaction_id) { SecureRandom.hex(16) }
8
+
9
+ def parse(builder)
10
+ Nokogiri::XML(builder.to_xml).tap(&:remove_namespaces!)
11
+ end
12
+
13
+ # ─── Shared examples ───────────────────────────────────────
14
+
15
+ shared_examples 'a base factory' do |factory_class|
16
+ subject(:factory) { factory_class.new(client) }
17
+
18
+ describe '#create_hev' do
19
+ it 'creates ebicsHEVRequest with HostID' do
20
+ xml = parse(factory.create_hev)
21
+ expect(xml.root.name).to eq('ebicsHEVRequest')
22
+ expect(xml.at('//HostID').text).to eq('SIZBN001')
23
+ end
24
+ end
25
+
26
+ describe '#create_ini' do
27
+ it 'creates ebicsUnsecuredRequest with INI order details' do
28
+ xml = parse(factory.create_ini('<order_data/>'))
29
+ expect(xml.root.name).to eq('ebicsUnsecuredRequest')
30
+ expect(xml.at('//HostID').text).to eq('SIZBN001')
31
+ expect(xml.at('//PartnerID').text).to eq(client.partner_id)
32
+ expect(xml.at('//UserID').text).to eq(client.user_id)
33
+ expect(xml.at('//SecurityMedium').text).to eq('0000')
34
+ end
35
+ end
36
+
37
+ describe '#create_hia' do
38
+ it 'creates ebicsUnsecuredRequest with HIA order details' do
39
+ xml = parse(factory.create_hia('<order_data/>'))
40
+ expect(xml.root.name).to eq('ebicsUnsecuredRequest')
41
+ expect(xml.at('//HostID').text).to eq('SIZBN001')
42
+ end
43
+ end
44
+
45
+ describe '#create_hpb' do
46
+ it 'creates ebicsNoPubKeyDigestsRequest with AuthSignature' do
47
+ xml = parse(factory.create_hpb)
48
+ expect(xml.root.name).to eq('ebicsNoPubKeyDigestsRequest')
49
+ expect(xml.at('//AuthSignature')).not_to be_nil
50
+ expect(xml.at('//SecurityMedium').text).to eq('0000')
51
+ end
52
+ end
53
+
54
+ describe '#create_transfer_receipt' do
55
+ it 'creates receipt with TransactionID and ReceiptCode' do
56
+ xml = parse(factory.create_transfer_receipt(transaction_id, 0))
57
+ expect(xml.root.name).to eq('ebicsRequest')
58
+ expect(xml.at('//TransactionID').text).to eq(transaction_id)
59
+ expect(xml.at('//TransactionPhase').text).to eq('Receipt')
60
+ expect(xml.at('//ReceiptCode').text).to eq('0')
61
+ expect(xml.at('//AuthSignature')).not_to be_nil
62
+ end
63
+ end
64
+
65
+ describe '#create_transfer_upload' do
66
+ it 'creates transfer with segment data' do
67
+ xml = parse(factory.create_transfer_upload(transaction_id, transaction_key, 'order_data', 1, true))
68
+ expect(xml.at('//TransactionID').text).to eq(transaction_id)
69
+ expect(xml.at('//TransactionPhase').text).to eq('Transfer')
70
+ node = xml.at('//SegmentNumber')
71
+ expect(node.text).to eq('1')
72
+ expect(node['lastSegment']).to eq('true')
73
+ expect(xml.at('//OrderData')).not_to be_nil
74
+ end
75
+ end
76
+
77
+ describe '#create_transfer_download' do
78
+ it 'creates transfer without order data' do
79
+ xml = parse(factory.create_transfer_download(transaction_id, 2, false))
80
+ expect(xml.at('//TransactionID').text).to eq(transaction_id)
81
+ expect(xml.at('//TransactionPhase').text).to eq('Transfer')
82
+ expect(xml.at('//SegmentNumber').text).to eq('2')
83
+ end
84
+ end
85
+
86
+ describe 'admin order types' do
87
+ %w[hpd hkd htd haa].each do |order_type|
88
+ it "creates #{order_type.upcase} request with AuthSignature" do
89
+ xml = parse(factory.send("create_#{order_type}"))
90
+ expect(xml.root.name).to eq('ebicsRequest')
91
+ expect(xml.at('//AuthSignature')).not_to be_nil
92
+ expect(xml.at('//TransactionPhase').text).to eq('Initialisation')
93
+ expect(xml.at('//BankPubKeyDigests')).not_to be_nil
94
+ end
95
+ end
96
+
97
+ %w[hac ptk].each do |order_type|
98
+ it "creates #{order_type.upcase} request with date range" do
99
+ xml = parse(factory.send("create_#{order_type}", start_date, end_date))
100
+ expect(xml.at('//AuthSignature')).not_to be_nil
101
+ expect(xml.at('//DateRange/Start').text).to eq('2024-01-01')
102
+ expect(xml.at('//DateRange/End').text).to eq('2024-01-31')
103
+ end
104
+ end
105
+ end
106
+ end
107
+
108
+ # ─── V2 shared examples ────────────────────────────────────
109
+
110
+ shared_examples 'a V2 download factory' do |factory_class, namespace|
111
+ subject(:factory) { factory_class.new(client) }
112
+
113
+ describe 'download requests' do
114
+ download_types = {
115
+ 'c52' => {}, 'c53' => {}, 'c54' => {}, 'sta' => {},
116
+ 'vmk' => {}, 'z52' => {}, 'z53' => {}, 'z54' => {},
117
+ 'bka' => {}, 'c5n' => {}, 'cdz' => {}, 'crz' => {}, 'z01' => {}
118
+ }
119
+
120
+ download_types.each do |order_type, _|
121
+ it "creates #{order_type.upcase} download request" do
122
+ xml = parse(factory.send("create_#{order_type}", start_date, end_date))
123
+ expect(xml.root.name).to eq('ebicsRequest')
124
+ expect(xml.at('//OrderType').text).to eq(order_type.upcase)
125
+ expect(xml.at('//OrderAttribute').text).to eq('DZHNN')
126
+ expect(xml.at('//AuthSignature')).not_to be_nil
127
+ expect(xml.at('//BankPubKeyDigests')).not_to be_nil
128
+ expect(xml.at('//DateRange/Start').text).to eq('2024-01-01')
129
+ end
130
+ end
131
+ end
132
+
133
+ describe 'WSS download (no date range)' do
134
+ it 'creates WSS request without date range' do
135
+ xml = parse(factory.create_wss)
136
+ expect(xml.at('//OrderType').text).to eq('WSS')
137
+ expect(xml.at('//DateRange')).to be_nil
138
+ end
139
+ end
140
+
141
+ describe 'FDL download with file format' do
142
+ it 'creates FDL request with FDLOrderParams' do
143
+ xml = parse(factory.create_fdl('camt.053', start_date, end_date))
144
+ expect(xml.at('//OrderType').text).to eq('FDL')
145
+ expect(xml.at('//FDLOrderParams/FileFormat').text).to eq('camt.053')
146
+ end
147
+ end
148
+ end
149
+
150
+ shared_examples 'a V2 upload factory' do |factory_class|
151
+ subject(:factory) { factory_class.new(client) }
152
+
153
+ describe 'upload requests' do
154
+ upload_types_with_es = {
155
+ 'cct' => true, 'cdd' => true, 'cdb' => true, 'cip' => true,
156
+ 'cd1' => true, 'b2b' => true, 'xds' => true, 'xe2' => true, 'xe3' => true
157
+ }
158
+ upload_types_without_es = {
159
+ 'ccs' => false, 'cds' => false, 'c2s' => false
160
+ }
161
+
162
+ (upload_types_with_es.merge(upload_types_without_es)).each do |order_type, with_es|
163
+ expected_attr = with_es ? 'OZHNN' : 'DZHNN'
164
+ it "creates #{order_type.upcase} upload with OrderAttribute=#{expected_attr}" do
165
+ xml = parse(factory.send("create_#{order_type}", digest, transaction_key))
166
+ expect(xml.root.name).to eq('ebicsRequest')
167
+ expect(xml.at('//OrderType').text).to eq(order_type.upcase)
168
+ expect(xml.at('//OrderAttribute').text).to eq(expected_attr)
169
+ expect(xml.at('//NumSegments').text).to eq('1')
170
+ expect(xml.at('//SignatureData')).not_to be_nil
171
+ expect(xml.at('//DataEncryptionInfo')).not_to be_nil
172
+ expect(xml.at('//TransactionKey')).not_to be_nil
173
+ expect(xml.at('//AuthSignature')).not_to be_nil
174
+ end
175
+ end
176
+ end
177
+
178
+ describe 'FUL upload with file format' do
179
+ it 'creates FUL request with FULOrderParams' do
180
+ xml = parse(factory.send(:create_ful, digest, transaction_key, file_format: 'pain.001.001.02'))
181
+ expect(xml.at('//OrderType').text).to eq('FUL')
182
+ expect(xml.at('//FULOrderParams/FileFormat').text).to eq('pain.001.001.02')
183
+ end
184
+ end
185
+
186
+ describe 'VersionSupportError for V3-only types' do
187
+ it 'raises for create_btd' do
188
+ expect { factory.create_btd }.to raise_error(Epics::VersionSupportError)
189
+ end
190
+
191
+ it 'raises for create_btu' do
192
+ expect { factory.create_btu }.to raise_error(Epics::VersionSupportError)
193
+ end
194
+ end
195
+ end
196
+
197
+ # ─── V24 (H003) ────────────────────────────────────────────
198
+
199
+ describe Epics::Factories::RequestFactory::V24 do
200
+ let(:version) { Epics::Keyring::VERSION_24 }
201
+
202
+ include_examples 'a base factory', Epics::Factories::RequestFactory::V24
203
+ include_examples 'a V2 download factory', Epics::Factories::RequestFactory::V24, 'http://www.ebics.org/H003'
204
+ include_examples 'a V2 upload factory', Epics::Factories::RequestFactory::V24
205
+
206
+ subject(:factory) { described_class.new(client) }
207
+
208
+ describe 'V24-specific: OrderID present' do
209
+ it 'includes OrderID in download requests' do
210
+ xml = parse(factory.create_c52(start_date, end_date))
211
+ expect(xml.at('//OrderID')).not_to be_nil
212
+ end
213
+
214
+ it 'includes OrderID in upload requests' do
215
+ xml = parse(factory.create_cct(digest, transaction_key))
216
+ expect(xml.at('//OrderID')).not_to be_nil
217
+ end
218
+ end
219
+
220
+ describe 'INI uses DZNNN attribute' do
221
+ it 'sets OrderAttribute to DZNNN for INI' do
222
+ xml = parse(factory.create_ini('<data/>'))
223
+ expect(xml.at('//OrderAttribute').text).to eq('DZNNN')
224
+ end
225
+ end
226
+
227
+ describe 'H005-only order types' do
228
+ %w[xek yct zsr].each do |type|
229
+ it "raises VersionSupportError for #{type}" do
230
+ expect { factory.send("create_#{type}", start_date, end_date) }
231
+ .to raise_error(Epics::VersionSupportError, 'Supported from version 3.0')
232
+ end
233
+ end
234
+ end
235
+
236
+ describe 'XML namespace' do
237
+ it 'uses H003 namespace' do
238
+ builder = factory.create_hev
239
+ xml = Nokogiri::XML(builder.to_xml)
240
+ # HEV uses H000, so check a standard request
241
+ builder2 = factory.create_hpb
242
+ xml2 = Nokogiri::XML(builder2.to_xml)
243
+ expect(xml2.root.namespace.href).to eq('http://www.ebics.org/H003')
244
+ end
245
+ end
246
+ end
247
+
248
+ # ─── V25 (H004) ────────────────────────────────────────────
249
+
250
+ describe Epics::Factories::RequestFactory::V25 do
251
+ let(:version) { Epics::Keyring::VERSION_25 }
252
+
253
+ include_examples 'a base factory', Epics::Factories::RequestFactory::V25
254
+ include_examples 'a V2 download factory', Epics::Factories::RequestFactory::V25, 'urn:org:ebics:H004'
255
+ include_examples 'a V2 upload factory', Epics::Factories::RequestFactory::V25
256
+
257
+ subject(:factory) { described_class.new(client) }
258
+
259
+ describe 'V25-specific: no OrderID' do
260
+ it 'does not include OrderID in download requests' do
261
+ xml = parse(factory.create_c52(start_date, end_date))
262
+ expect(xml.at('//OrderID')).to be_nil
263
+ end
264
+ end
265
+
266
+ describe 'XML namespace' do
267
+ it 'uses H004 namespace' do
268
+ builder = factory.create_hpb
269
+ xml = Nokogiri::XML(builder.to_xml)
270
+ expect(xml.root.namespace.href).to eq('urn:org:ebics:H004')
271
+ end
272
+ end
273
+ end
274
+
275
+ # ─── V3 (H005) ────────────────────────────────────────────
276
+
277
+ describe Epics::Factories::RequestFactory::V3 do
278
+ let(:version) { Epics::Keyring::VERSION_30 }
279
+
280
+ include_examples 'a base factory', Epics::Factories::RequestFactory::V3
281
+
282
+ subject(:factory) { described_class.new(client) }
283
+
284
+ describe 'XML namespace' do
285
+ it 'uses H005 namespace' do
286
+ builder = factory.create_hpb
287
+ xml = Nokogiri::XML(builder.to_xml)
288
+ expect(xml.root.namespace.href).to eq('urn:org:ebics:H005')
289
+ end
290
+ end
291
+
292
+ describe 'uses AdminOrderType instead of OrderType' do
293
+ it 'generates AdminOrderType for admin requests' do
294
+ xml = parse(factory.create_hpd)
295
+ expect(xml.at('//AdminOrderType').text).to eq('HPD')
296
+ expect(xml.at('//OrderType')).to be_nil
297
+ expect(xml.at('//OrderAttribute')).to be_nil
298
+ expect(xml.at('//OrderID')).to be_nil
299
+ end
300
+ end
301
+
302
+ describe 'BTD download requests' do
303
+ btd_downloads = {
304
+ 'c52' => { service_name: 'STM', msg_name: 'camt.052' },
305
+ 'c53' => { service_name: 'EOP', msg_name: 'camt.053', container_type: 'ZIP' },
306
+ 'c54' => { service_name: 'REP', msg_name: 'camt.054', container_type: 'ZIP' },
307
+ 'sta' => { service_name: 'EOP', msg_name: 'mt940' },
308
+ 'vmk' => { service_name: 'STM', msg_name: 'mt942' },
309
+ 'z52' => { service_name: 'STM', msg_name: 'camt.052', container_type: 'ZIP' },
310
+ 'z53' => { service_name: 'EOP', msg_name: 'camt.053', container_type: 'ZIP' },
311
+ 'z54' => { service_name: 'EOP', msg_name: 'camt.054', service_option: 'XQRR' },
312
+ 'xek' => { service_name: 'EOP', msg_name: 'pdf', container_type: 'ZIP' },
313
+ 'z01' => { service_name: 'PSR', msg_name: 'pain.002', service_option: 'CH003GEN' },
314
+ 'bka' => { service_name: 'EOP', msg_name: 'camt.053', scope: 'DE' },
315
+ 'c5n' => { service_name: 'STM', msg_name: 'camt.054', scope: 'DE', service_option: 'SCI' },
316
+ 'cdz' => { service_name: 'REP', msg_name: 'pain.002', scope: 'DE', service_option: 'SDD' },
317
+ 'crz' => { service_name: 'REP', msg_name: 'pain.002', scope: 'DE', service_option: 'SCT' },
318
+ 'zsr' => { service_name: 'PSR', msg_name: 'pain.002', scope: 'BIL' },
319
+ }
320
+
321
+ btd_downloads.each do |order_type, expected|
322
+ it "creates #{order_type.upcase} as BTD with correct service params" do
323
+ xml = parse(factory.send("create_#{order_type}", start_date, end_date))
324
+ expect(xml.at('//AdminOrderType').text).to eq('BTD')
325
+ expect(xml.at('//BTDOrderParams/Service/ServiceName').text).to eq(expected[:service_name])
326
+ expect(xml.at('//BTDOrderParams/Service/MsgName').text).to eq(expected[:msg_name])
327
+
328
+ if expected[:scope]
329
+ expect(xml.at('//BTDOrderParams/Service/Scope').text).to eq(expected[:scope])
330
+ else
331
+ expect(xml.at('//BTDOrderParams/Service/Scope')).to be_nil
332
+ end
333
+
334
+ if expected[:service_option]
335
+ expect(xml.at('//BTDOrderParams/Service/ServiceOption').text).to eq(expected[:service_option])
336
+ end
337
+
338
+ if expected[:container_type]
339
+ expect(xml.at('//BTDOrderParams/Service/Container')['containerType']).to eq(expected[:container_type])
340
+ end
341
+
342
+ expect(xml.at('//AuthSignature')).not_to be_nil
343
+ expect(xml.at('//BankPubKeyDigests')).not_to be_nil
344
+ end
345
+ end
346
+ end
347
+
348
+ describe 'BTU upload requests' do
349
+ btu_uploads = {
350
+ 'cct' => { service_name: 'SCT', msg_name: 'pain.001', scope: 'DE', filename: 'cct.pain.001.xxx.xml' },
351
+ 'cdd' => { service_name: 'SDD', msg_name: 'pain.008', scope: 'DE', service_option: 'COR', filename: 'cdd.pain.008.xxx.xml' },
352
+ 'cdb' => { service_name: 'SDD', msg_name: 'pain.008', service_option: 'B2B', filename: 'cdb.pain.008.xxx.xml' },
353
+ 'cip' => { service_name: 'SCI', msg_name: 'pain.001', filename: 'cip.pain.001.xxx.xml' },
354
+ 'xe2' => { service_name: 'MCT', msg_name: 'pain.001', filename: 'xe2.pain.001.xxx.xml' },
355
+ 'xe3' => { service_name: 'SDD', msg_name: 'pain.008', filename: 'xe3.pain.008.xxx.xml' },
356
+ 'yct' => { service_name: 'MCT', msg_name: 'pain.001', scope: 'BIL', filename: 'yct.pain.001.xxx.xml' },
357
+ 'azv' => { service_name: 'XCT', msg_name: 'dtazv', scope: 'DE', filename: 'azv.dtazv.xxx.xml' },
358
+ 'b2b' => { service_name: 'SDD', msg_name: 'pain.008', scope: 'BIL', service_option: 'B2B', filename: 'b2b.pain.008.xxx.xml' },
359
+ 'ccs' => { service_name: 'SCT', msg_name: 'pain.001', scope: 'DE', filename: 'ccs.pain.001.xxx.xml' },
360
+ 'cds' => { service_name: 'SDD', msg_name: 'pain.008', scope: 'BIL', filename: 'cds.pain.008.xxx.xml' },
361
+ 'c2s' => { service_name: 'SDD', msg_name: 'pain.008', scope: 'BIL', filename: 'c2s.pain.008.xxx.xml' },
362
+ }
363
+
364
+ btu_uploads.each do |order_type, expected|
365
+ it "creates #{order_type.upcase} as BTU with correct service params" do
366
+ xml = parse(factory.send("create_#{order_type}", digest, transaction_key))
367
+ expect(xml.at('//AdminOrderType').text).to eq('BTU')
368
+ params = xml.at('//BTUOrderParams')
369
+ expect(params['fileName']).to eq(expected[:filename])
370
+ expect(params.at('Service/ServiceName').text).to eq(expected[:service_name])
371
+ expect(params.at('Service/MsgName').text).to eq(expected[:msg_name])
372
+
373
+ if expected[:scope]
374
+ expect(params.at('Service/Scope').text).to eq(expected[:scope])
375
+ else
376
+ expect(params.at('Service/Scope')).to be_nil
377
+ end
378
+
379
+ if expected[:service_option]
380
+ expect(params.at('Service/ServiceOption').text).to eq(expected[:service_option])
381
+ end
382
+
383
+ # V3-specific upload elements
384
+ expect(xml.at('//DataDigest')).not_to be_nil
385
+ expect(xml.at('//AdditionalOrderInfo')).not_to be_nil
386
+ expect(xml.at('//SignatureData')).not_to be_nil
387
+ expect(xml.at('//DataEncryptionInfo')).not_to be_nil
388
+ expect(xml.at('//AuthSignature')).not_to be_nil
389
+ expect(xml.at('//NumSegments').text).to eq('1')
390
+ end
391
+ end
392
+ end
393
+
394
+ describe 'VersionSupportError for V2-only types' do
395
+ %w[ful fdl cd1 wss xds].each do |type|
396
+ it "raises VersionSupportError for #{type}" do
397
+ expect { factory.send("create_#{type}", digest, transaction_key) }.to raise_error(Epics::VersionSupportError)
398
+ end
399
+ end
400
+ end
401
+
402
+ describe 'V3 upload includes DataDigest with correct SignatureVersion' do
403
+ it 'DataDigest has SignatureVersion matching user signature' do
404
+ xml = parse(factory.create_cct(digest, transaction_key))
405
+ data_digest = xml.at('//DataDigest')
406
+ expect(data_digest['SignatureVersion']).to eq(client.keyring.user_signature.version)
407
+ end
408
+ end
409
+
410
+ describe 'BTU upload override passthrough' do
411
+ it 'create_cct accepts service_option override' do
412
+ xml = parse(factory.create_cct(digest, transaction_key, service_option: 'VOO'))
413
+ params = xml.at('//BTUOrderParams')
414
+ expect(params.at('Service/ServiceName').text).to eq('SCT')
415
+ expect(params.at('Service/ServiceOption').text).to eq('VOO')
416
+ expect(params.at('Service/Scope').text).to eq('DE')
417
+ end
418
+
419
+ it 'create_cct without override has no ServiceOption' do
420
+ xml = parse(factory.create_cct(digest, transaction_key))
421
+ params = xml.at('//BTUOrderParams')
422
+ expect(params.at('Service/ServiceOption')).to be_nil
423
+ end
424
+
425
+ it 'create_cdd override replaces default service_option' do
426
+ xml = parse(factory.create_cdd(digest, transaction_key, service_option: 'NEW'))
427
+ params = xml.at('//BTUOrderParams')
428
+ expect(params.at('Service/ServiceOption').text).to eq('NEW')
429
+ end
430
+
431
+ it 'create_cdb override can change scope' do
432
+ xml = parse(factory.create_cdb(digest, transaction_key, scope: 'CH'))
433
+ params = xml.at('//BTUOrderParams')
434
+ expect(params.at('Service/Scope').text).to eq('CH')
435
+ end
436
+
437
+ it 'override can change filename' do
438
+ xml = parse(factory.create_cct(digest, transaction_key, filename: 'custom.xml'))
439
+ params = xml.at('//BTUOrderParams')
440
+ expect(params['fileName']).to eq('custom.xml')
441
+ end
442
+ end
443
+ end
444
+ end
@@ -0,0 +1,45 @@
1
+ RSpec.describe Epics::Response do
2
+ let(:client) do
3
+ Epics::Client.new(File.open(File.join(File.dirname(__FILE__), 'fixtures', 'SIZBN001.key')),
4
+ 'secret', 'https://194.180.18.30/ebicsweb/ebicsweb', 'SIZBN001', 'EBIX', 'EBICS')
5
+ end
6
+
7
+ def response_with(code)
8
+ xml = <<~XML
9
+ <?xml version="1.0" encoding="UTF-8"?>
10
+ <ebicsResponse xmlns="urn:org:ebics:H004">
11
+ <body><ReturnCode>#{code}</ReturnCode></body>
12
+ </ebicsResponse>
13
+ XML
14
+ described_class.new(client, xml)
15
+ end
16
+
17
+ describe '#business_error?' do
18
+ { '000000' => false,
19
+ '011000' => false, # positive acknowledgement
20
+ '011301' => false } # bank does not support prevalidation
21
+ .each do |code, expected|
22
+ it "treats #{code} as #{expected ? 'an error' : 'informational'}" do
23
+ expect(response_with(code).business_error?).to eq(expected)
24
+ end
25
+ end
26
+
27
+ # Both are defined as failures in Epics::Error and share the 01 prefix with the
28
+ # informational codes above.
29
+ { '011001' => 'negative acknowledgement received',
30
+ '011101' => 'segment number not reached' }.each do |code, description|
31
+ it "treats #{code} (#{description}) as an error" do
32
+ expect(response_with(code).business_error?).to be(true)
33
+ end
34
+ end
35
+
36
+ it 'treats an absent return code as informational' do
37
+ xml = '<?xml version="1.0"?><ebicsResponse xmlns="urn:org:ebics:H004"><body/></ebicsResponse>'
38
+ expect(described_class.new(client, xml).business_error?).to be(false)
39
+ end
40
+
41
+ it 'treats an unrelated failure code as an error' do
42
+ expect(response_with('091010').business_error?).to be(true)
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,23 @@
1
+ RSpec.describe 'Epics::Response validity checks without keys' do
2
+ let(:client) { Epics::Client.new(nil, 'secret', 'https://example.com/ebics', 'HOST01', 'USER01', 'PARTNER01') }
3
+ let(:xml) { File.read(File.join(File.dirname(__FILE__), 'fixtures', 'xml', 'upload_init_response.xml')) }
4
+
5
+ subject(:response) { Epics::Response.new(client, xml) }
6
+
7
+ # A response can arrive before HPB has run, so these must say what is missing rather
8
+ # than fail on nil.
9
+ it 'reports the missing bank key when verifying a signature' do
10
+ expect { response.signature_valid? }
11
+ .to raise_error(Epics::MissingKeyError, /bank's authentication key/)
12
+ end
13
+
14
+ it 'reports the missing encryption key when comparing the public digest' do
15
+ expect { response.public_digest_valid? }
16
+ .to raise_error(Epics::MissingKeyError, /encryption key/)
17
+ end
18
+
19
+ # The digest is a plain SHA-256 over the authenticated nodes and needs no key at all.
20
+ it 'checks the digest without any key loaded' do
21
+ expect { response.digest_valid? }.not_to raise_error
22
+ end
23
+ end
@@ -1,6 +1,6 @@
1
- RSpec.describe Epics::Key do
1
+ RSpec.describe Epics::SignatureAlgorithm::RsaPss do
2
2
 
3
- subject { described_class.new( File.read(File.join( File.dirname(__FILE__), 'fixtures', 'e002.pem'))) }
3
+ subject { described_class.new( File.read(File.join( File.dirname(__FILE__), '..', 'fixtures', 'e002.pem'))) }
4
4
 
5
5
  describe '#public_digest' do
6
6
 
@@ -14,17 +14,8 @@ RSpec.describe Epics::Key do
14
14
  let(:dsi) { OpenSSL::Digest::SHA256.new.digest("ruby is great") }
15
15
 
16
16
  it 'will generated a digest that can be verified with openssl key.verify_pss' do
17
- signed_digest = subject.sign(dsi)
18
-
19
- key = subject.key
20
-
21
- verification_result = key.verify_pss(
22
- 'SHA256',
23
- Base64.decode64(signed_digest),
24
- dsi,
25
- salt_length: :digest,
26
- mgf1_hash: 'SHA256',
27
- )
17
+ signed_digest = Base64.encode64(subject.sign(dsi)).strip
18
+ verification_result = subject.verify(signed_digest, dsi)
28
19
 
29
20
  expect(verification_result).to eq(true)
30
21
  end
@@ -1,22 +1,30 @@
1
- RSpec::Matchers.define :be_a_valid_ebics_doc do
2
-
1
+ RSpec::Matchers.define :be_a_valid_ebics_doc do |version|
3
2
  ##
4
3
  # use #open instead of #read to have the includes working
5
4
  # http://stackoverflow.com/questions/11996326/nokogirixmlschema-syntaxerror-on-schema-load/22971456#22971456
6
- def xsd
7
- @xsd ||= Nokogiri::XML::Schema(File.open( File.join( File.dirname(__FILE__), '..', 'xsd', 'ebics_H004.xsd') ))
5
+ def xsd(version)
6
+ @xsd ||= {}
7
+ @xsd[version] ||= Nokogiri::XML::Schema(case version
8
+ when Epics::Keyring::VERSION_30
9
+ File.open( File.join( File.dirname(__FILE__), '..', 'xsd', 'H005', 'ebics_H005.xsd') )
10
+ when Epics::Keyring::VERSION_25
11
+ File.open( File.join( File.dirname(__FILE__), '..', 'xsd', 'H004', 'ebics_H004.xsd') )
12
+ when Epics::Keyring::VERSION_24
13
+ File.open( File.join( File.dirname(__FILE__), '..', 'xsd', 'H003', 'ebics.xsd') )
14
+ when 'H000'
15
+ File.open( File.join( File.dirname(__FILE__), '..', 'xsd', 'H000', 'ebics_hev.xsd') )
16
+ end)
8
17
  end
9
18
 
10
19
  match do |actual|
11
- xsd.valid?(Nokogiri::XML(actual))
20
+ xsd(version).valid?(Nokogiri::XML(actual))
12
21
  end
13
22
 
14
23
  failure_message do |actual|
15
- "expected that #{actual} would be a valid EBICS doc:\n\n #{xsd.validate(Nokogiri::XML(actual))}"
24
+ "expected that #{actual} would be a valid EBICS doc:\n\n #{xsd(version).validate(Nokogiri::XML(actual))}"
16
25
  end
17
26
 
18
27
  description do
19
- "be a valid EBICS document"
28
+ 'be a valid EBICS document'
20
29
  end
21
-
22
- end
30
+ end