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,616 @@
1
+ RSpec.describe 'Builders' do
2
+ # Helper to parse XML from a builder's doc
3
+ def parse_doc(builder_instance)
4
+ Nokogiri::XML(builder_instance.doc.to_xml)
5
+ end
6
+
7
+ describe Epics::Builders::XmlBuilder do
8
+ shared_examples 'an XmlBuilder version' do |version_class, h00x_version, h00x_namespace|
9
+ subject(:builder) { version_class.new }
10
+
11
+ describe '#create_secured' do
12
+ it "creates ebicsRequest with version #{h00x_version}" do
13
+ builder.create_secured { |b| }
14
+ xml = Nokogiri::XML(builder.to_xml)
15
+ root = xml.root
16
+ expect(root.name).to eq('ebicsRequest')
17
+ expect(root['Version']).to eq(h00x_version)
18
+ expect(root['Revision']).to eq('1')
19
+ expect(root.namespace.href).to eq(h00x_namespace)
20
+ end
21
+
22
+ it 'includes ds namespace for secured requests' do
23
+ builder.create_secured { |b| }
24
+ xml = Nokogiri::XML(builder.to_xml)
25
+ expect(xml.root.namespaces['xmlns:ds']).to eq('http://www.w3.org/2000/09/xmldsig#')
26
+ end
27
+ end
28
+
29
+ describe '#create_unsecured' do
30
+ it 'creates ebicsUnsecuredRequest without ds namespace' do
31
+ builder.create_unsecured { |b| }
32
+ xml = Nokogiri::XML(builder.to_xml)
33
+ expect(xml.root.name).to eq('ebicsUnsecuredRequest')
34
+ expect(xml.root.namespaces['xmlns:ds']).to be_nil
35
+ end
36
+ end
37
+
38
+ describe '#create_secured_no_pubkey_digests' do
39
+ it 'creates ebicsNoPubKeyDigestsRequest' do
40
+ builder.create_secured_no_pubkey_digests { |b| }
41
+ xml = Nokogiri::XML(builder.to_xml)
42
+ expect(xml.root.name).to eq('ebicsNoPubKeyDigestsRequest')
43
+ expect(xml.root.namespaces['xmlns:ds']).to eq('http://www.w3.org/2000/09/xmldsig#')
44
+ end
45
+ end
46
+
47
+ describe '#create_unsigned' do
48
+ it 'creates ebicsUnsignedRequest' do
49
+ builder.create_unsigned { |b| }
50
+ xml = Nokogiri::XML(builder.to_xml)
51
+ expect(xml.root.name).to eq('ebicsUnsignedRequest')
52
+ end
53
+ end
54
+ end
55
+
56
+ context 'V24 (H003)' do
57
+ include_examples 'an XmlBuilder version',
58
+ Epics::Builders::XmlBuilder::V24, 'H003', 'http://www.ebics.org/H003'
59
+ end
60
+
61
+ context 'V25 (H004)' do
62
+ include_examples 'an XmlBuilder version',
63
+ Epics::Builders::XmlBuilder::V25, 'H004', 'urn:org:ebics:H004'
64
+ end
65
+
66
+ context 'V3 (H005)' do
67
+ include_examples 'an XmlBuilder version',
68
+ Epics::Builders::XmlBuilder::V3, 'H005', 'urn:org:ebics:H005'
69
+ end
70
+
71
+ describe '::Base#create_hev' do
72
+ it 'creates ebicsHEVRequest with H000 namespace' do
73
+ builder = Epics::Builders::XmlBuilder::V25.new
74
+ builder.create_hev { |b| }
75
+ xml = Nokogiri::XML(builder.to_xml)
76
+ expect(xml.root.name).to eq('ebicsHEVRequest')
77
+ expect(xml.root.namespace.href).to eq('http://www.ebics.org/H000')
78
+ end
79
+ end
80
+ end
81
+
82
+ describe Epics::Builders::MutableBuilder do
83
+ it 'creates mutable element with TransactionPhase' do
84
+ instance = described_class.new do |b|
85
+ b.add_transaction_phase Epics::Builders::MutableBuilder::PHASE_INITIALIZATION
86
+ end
87
+ xml = parse_doc(instance)
88
+ expect(xml.at('mutable/TransactionPhase').text).to eq('Initialisation')
89
+ end
90
+
91
+ it 'creates SegmentNumber with lastSegment attribute' do
92
+ instance = described_class.new do |b|
93
+ b.add_segment_number 3, true
94
+ end
95
+ xml = parse_doc(instance)
96
+ node = xml.at('mutable/SegmentNumber')
97
+ expect(node.text).to eq('3')
98
+ expect(node['lastSegment']).to eq('true')
99
+ end
100
+
101
+ end
102
+
103
+ describe Epics::Builders::TransferReceiptBuilder do
104
+ it 'creates TransferReceipt with authenticate=true and ReceiptCode' do
105
+ instance = described_class.new do |b|
106
+ b.add_receipt_code 0
107
+ end
108
+ xml = parse_doc(instance)
109
+ root = xml.root
110
+ expect(root.name).to eq('TransferReceipt')
111
+ expect(root['authenticate']).to eq('true')
112
+ expect(root.at('ReceiptCode').text).to eq('0')
113
+ end
114
+ end
115
+
116
+ describe Epics::Builders::OrderDetailsBuilder do
117
+ describe '::V2' do
118
+ it 'creates OrderType and OrderAttribute elements' do
119
+ instance = described_class::V2.new do |b|
120
+ b.add_order_type 'CCT'
121
+ b.add_order_attribute 'DZHNN'
122
+ end
123
+ xml = parse_doc(instance)
124
+ expect(xml.at('OrderDetails/OrderType').text).to eq('CCT')
125
+ expect(xml.at('OrderDetails/OrderAttribute').text).to eq('DZHNN')
126
+ end
127
+
128
+ it 'raises VersionSupportError for add_admin_order_type' do
129
+ expect do
130
+ described_class::V2.new { |b| b.add_admin_order_type 'BTD' }
131
+ end.to raise_error(Epics::VersionSupportError)
132
+ end
133
+
134
+ it 'raises VersionSupportError for add_btd_order_params' do
135
+ expect do
136
+ described_class::V2.new { |b| b.add_btd_order_params }
137
+ end.to raise_error(Epics::VersionSupportError)
138
+ end
139
+
140
+ it 'raises VersionSupportError for add_btu_order_params' do
141
+ expect do
142
+ described_class::V2.new { |b| b.add_btu_order_params }
143
+ end.to raise_error(Epics::VersionSupportError)
144
+ end
145
+
146
+ it 'creates StandardOrderParams with DateRange' do
147
+ instance = described_class::V2.new do |b|
148
+ b.add_standard_order_params(Date.new(2024, 1, 1), Date.new(2024, 1, 31))
149
+ end
150
+ xml = parse_doc(instance)
151
+ expect(xml.at('StandardOrderParams/DateRange/Start').text).to eq('2024-01-01')
152
+ expect(xml.at('StandardOrderParams/DateRange/End').text).to eq('2024-01-31')
153
+ end
154
+
155
+ it 'creates StandardOrderParams without DateRange' do
156
+ instance = described_class::V2.new do |b|
157
+ b.add_standard_order_params
158
+ end
159
+ xml = parse_doc(instance)
160
+ expect(xml.at('StandardOrderParams')).not_to be_nil
161
+ expect(xml.at('StandardOrderParams/DateRange')).to be_nil
162
+ end
163
+
164
+ it 'creates FDLOrderParams with FileFormat' do
165
+ instance = described_class::V2.new do |b|
166
+ b.add_fdl_order_params('camt.053')
167
+ end
168
+ xml = parse_doc(instance)
169
+ expect(xml.at('FDLOrderParams/FileFormat').text).to eq('camt.053')
170
+ end
171
+
172
+ it 'creates FDLOrderParams with FileFormat and DateRange' do
173
+ instance = described_class::V2.new do |b|
174
+ b.add_fdl_order_params('camt.053', Date.new(2024, 1, 1), Date.new(2024, 1, 31))
175
+ end
176
+ xml = parse_doc(instance)
177
+ expect(xml.at('FDLOrderParams/FileFormat').text).to eq('camt.053')
178
+ expect(xml.at('FDLOrderParams/DateRange/Start').text).to eq('2024-01-01')
179
+ end
180
+
181
+ it 'creates FULOrderParams with FileFormat' do
182
+ instance = described_class::V2.new do |b|
183
+ b.add_ful_order_params('pain.001.001.02')
184
+ end
185
+ xml = parse_doc(instance)
186
+ expect(xml.at('FULOrderParams/FileFormat').text).to eq('pain.001.001.02')
187
+ end
188
+
189
+ it 'creates OrderID in base36 uppercase right-justified' do
190
+ instance = described_class::V2.new do |b|
191
+ b.add_order_id(100)
192
+ end
193
+ xml = parse_doc(instance)
194
+ expect(xml.at('OrderDetails/OrderID').text).to eq('002S')
195
+ end
196
+ end
197
+
198
+ describe '::V3' do
199
+ it 'creates AdminOrderType element' do
200
+ instance = described_class::V3.new do |b|
201
+ b.add_admin_order_type 'BTD'
202
+ end
203
+ xml = parse_doc(instance)
204
+ expect(xml.at('OrderDetails/AdminOrderType').text).to eq('BTD')
205
+ end
206
+
207
+ it 'raises VersionSupportError for add_order_type' do
208
+ expect do
209
+ described_class::V3.new { |b| b.add_order_type 'CCT' }
210
+ end.to raise_error(Epics::VersionSupportError)
211
+ end
212
+
213
+ it 'raises VersionSupportError for add_order_attribute' do
214
+ expect do
215
+ described_class::V3.new { |b| b.add_order_attribute 'DZHNN' }
216
+ end.to raise_error(Epics::VersionSupportError)
217
+ end
218
+
219
+ describe '#add_btd_order_params' do
220
+ it 'creates BTDOrderParams with Service structure' do
221
+ instance = described_class::V3.new do |b|
222
+ b.add_btd_order_params(
223
+ service_name: 'EOP', msg_name: 'camt.053',
224
+ container_type: 'ZIP', scope: 'DE', service_option: 'SCT'
225
+ )
226
+ end
227
+ xml = parse_doc(instance)
228
+ params = xml.at('OrderDetails/BTDOrderParams')
229
+ expect(params).not_to be_nil
230
+ expect(params.at('Service/ServiceName').text).to eq('EOP')
231
+ expect(params.at('Service/Scope').text).to eq('DE')
232
+ expect(params.at('Service/ServiceOption').text).to eq('SCT')
233
+ expect(params.at('Service/Container')['containerType']).to eq('ZIP')
234
+ expect(params.at('Service/MsgName').text).to eq('camt.053')
235
+ end
236
+
237
+ it 'omits optional fields when not provided' do
238
+ instance = described_class::V3.new do |b|
239
+ b.add_btd_order_params(service_name: 'STM', msg_name: 'camt.052')
240
+ end
241
+ xml = parse_doc(instance)
242
+ params = xml.at('OrderDetails/BTDOrderParams')
243
+ expect(params.at('Service/Scope')).to be_nil
244
+ expect(params.at('Service/ServiceOption')).to be_nil
245
+ expect(params.at('Service/Container')).to be_nil
246
+ expect(params.at('DateRange')).to be_nil
247
+ end
248
+
249
+ it 'includes DateRange when dates provided' do
250
+ instance = described_class::V3.new do |b|
251
+ b.add_btd_order_params(
252
+ service_name: 'EOP', msg_name: 'camt.053',
253
+ start_date: Date.new(2024, 3, 1), end_date: Date.new(2024, 3, 31)
254
+ )
255
+ end
256
+ xml = parse_doc(instance)
257
+ expect(xml.at('BTDOrderParams/DateRange/Start').text).to eq('2024-03-01')
258
+ expect(xml.at('BTDOrderParams/DateRange/End').text).to eq('2024-03-31')
259
+ end
260
+
261
+ it 'supports MsgName attributes' do
262
+ instance = described_class::V3.new do |b|
263
+ b.add_btd_order_params(
264
+ service_name: 'EOP', msg_name: 'camt.053',
265
+ msg_name_version: '08', msg_name_variant: 'gzip', msg_name_format: 'XML'
266
+ )
267
+ end
268
+ xml = parse_doc(instance)
269
+ msg = xml.at('BTDOrderParams/Service/MsgName')
270
+ expect(msg['version']).to eq('08')
271
+ expect(msg['variant']).to eq('gzip')
272
+ expect(msg['format']).to eq('XML')
273
+ end
274
+ end
275
+
276
+ describe '#add_btu_order_params' do
277
+ it 'creates BTUOrderParams with fileName and Service' do
278
+ instance = described_class::V3.new do |b|
279
+ b.add_btu_order_params(
280
+ filename: 'cct.pain.001.xxx.xml',
281
+ service_name: 'SCT', msg_name: 'pain.001', scope: 'DE'
282
+ )
283
+ end
284
+ xml = parse_doc(instance)
285
+ params = xml.at('OrderDetails/BTUOrderParams')
286
+ expect(params['fileName']).to eq('cct.pain.001.xxx.xml')
287
+ expect(params.at('Service/ServiceName').text).to eq('SCT')
288
+ expect(params.at('Service/Scope').text).to eq('DE')
289
+ expect(params.at('Service/MsgName').text).to eq('pain.001')
290
+ end
291
+
292
+ it 'omits optional fields when not provided' do
293
+ instance = described_class::V3.new do |b|
294
+ b.add_btu_order_params(
295
+ filename: 'test.xml', service_name: 'SCT', msg_name: 'pain.001'
296
+ )
297
+ end
298
+ xml = parse_doc(instance)
299
+ params = xml.at('OrderDetails/BTUOrderParams')
300
+ expect(params.at('Service/Scope')).to be_nil
301
+ expect(params.at('Service/ServiceOption')).to be_nil
302
+ expect(params.at('Service/ContainerFlag')).to be_nil
303
+ expect(params.at('Service/Container')).to be_nil
304
+ end
305
+
306
+ it 'generates Container element with containerType attribute' do
307
+ instance = described_class::V3.new do |b|
308
+ b.add_btu_order_params(
309
+ filename: 'cct.pain.001.xxx.xml',
310
+ service_name: 'SCT', scope: 'DE', msg_name: 'pain.001',
311
+ container_type: 'ZIP'
312
+ )
313
+ end
314
+ xml = parse_doc(instance)
315
+ params = xml.at('OrderDetails/BTUOrderParams')
316
+ container = params.at('Service/Container')
317
+ expect(container).not_to be_nil
318
+ expect(container['containerType']).to eq('ZIP')
319
+ end
320
+ end
321
+ end
322
+ end
323
+
324
+ describe Epics::Builders::StaticBuilder do
325
+ shared_examples 'a StaticBuilder' do |version_class|
326
+ it 'creates static element with all standard fields' do
327
+ timestamp = Time.utc(2024, 6, 15, 10, 30, 0)
328
+ allow(SecureRandom).to receive(:hex).with(16).and_return('abcdef1234567890abcdef1234567890')
329
+
330
+ instance = version_class.new do |b|
331
+ b.add_host_id 'SIZBN001'
332
+ b.add_random_nonce
333
+ b.add_timestamp timestamp
334
+ b.add_partner_id 'EBIX'
335
+ b.add_user_id 'EBICS'
336
+ b.add_product 'Epics', 'fr'
337
+ b.add_security_medium '0000'
338
+ end
339
+ xml = parse_doc(instance)
340
+ expect(xml.at('static/HostID').text).to eq('SIZBN001')
341
+ expect(xml.at('static/Nonce').text).to eq('abcdef1234567890abcdef1234567890')
342
+ expect(xml.at('static/Timestamp').text).to eq('2024-06-15T10:30:00Z')
343
+ expect(xml.at('static/PartnerID').text).to eq('EBIX')
344
+ expect(xml.at('static/UserID').text).to eq('EBICS')
345
+ expect(xml.at('static/Product').text).to eq('Epics')
346
+ expect(xml.at('static/Product')['Language']).to eq('fr')
347
+ expect(xml.at('static/SecurityMedium').text).to eq('0000')
348
+ end
349
+
350
+ it 'creates BankPubKeyDigests with Base64-encoded digests' do
351
+ instance = version_class.new do |b|
352
+ b.add_bank_pubbey_digests('X002', 'auth_digest', 'E002', 'enc_digest')
353
+ end
354
+ xml = parse_doc(instance)
355
+ auth = xml.at('static/BankPubKeyDigests/Authentication')
356
+ enc = xml.at('static/BankPubKeyDigests/Encryption')
357
+ expect(auth['Version']).to eq('X002')
358
+ expect(auth['Algorithm']).to eq('http://www.w3.org/2001/04/xmlenc#sha256')
359
+ expect(auth.text).to eq(Base64.strict_encode64('auth_digest'))
360
+ expect(enc['Version']).to eq('E002')
361
+ expect(enc.text).to eq(Base64.strict_encode64('enc_digest'))
362
+ end
363
+
364
+ it 'creates NumSegments element' do
365
+ instance = version_class.new do |b|
366
+ b.add_num_segments 5
367
+ end
368
+ xml = parse_doc(instance)
369
+ expect(xml.at('static/NumSegments').text).to eq('5')
370
+ end
371
+
372
+ it 'creates TransactionID element' do
373
+ instance = version_class.new do |b|
374
+ b.add_transaction_id 'abc123'
375
+ end
376
+ xml = parse_doc(instance)
377
+ expect(xml.at('static/TransactionID').text).to eq('abc123')
378
+ end
379
+ end
380
+
381
+ describe '::V2' do
382
+ include_examples 'a StaticBuilder', Epics::Builders::StaticBuilder::V2
383
+
384
+ it 'delegates add_order_details to OrderDetailsBuilder::V2' do
385
+ instance = Epics::Builders::StaticBuilder::V2.new do |b|
386
+ b.add_order_details do |od|
387
+ od.add_order_type 'CCT'
388
+ od.add_order_attribute 'DZHNN'
389
+ end
390
+ end
391
+ xml = parse_doc(instance)
392
+ expect(xml.at('static/OrderDetails/OrderType').text).to eq('CCT')
393
+ expect(xml.at('static/OrderDetails/OrderAttribute').text).to eq('DZHNN')
394
+ end
395
+ end
396
+
397
+ describe '::V3' do
398
+ include_examples 'a StaticBuilder', Epics::Builders::StaticBuilder::V3
399
+
400
+ it 'delegates add_order_details to OrderDetailsBuilder::V3' do
401
+ instance = Epics::Builders::StaticBuilder::V3.new do |b|
402
+ b.add_order_details do |od|
403
+ od.add_admin_order_type 'BTD'
404
+ od.add_btd_order_params(service_name: 'EOP', msg_name: 'camt.053')
405
+ end
406
+ end
407
+ xml = parse_doc(instance)
408
+ expect(xml.at('static/OrderDetails/AdminOrderType').text).to eq('BTD')
409
+ expect(xml.at('static/OrderDetails/BTDOrderParams/Service/ServiceName').text).to eq('EOP')
410
+ end
411
+ end
412
+ end
413
+
414
+ describe Epics::Builders::DataTransferBuilder do
415
+ describe '::V2' do
416
+ it 'add_data_digest is a no-op' do
417
+ instance = described_class::V2.new do |b|
418
+ result = b.add_data_digest('A005', 'some_digest')
419
+ expect(result).to eq(b)
420
+ end
421
+ xml = parse_doc(instance)
422
+ expect(xml.at('DataTransfer/DataDigest')).to be_nil
423
+ end
424
+
425
+ it 'add_additional_order_info is a no-op' do
426
+ instance = described_class::V2.new do |b|
427
+ result = b.add_additional_order_info
428
+ expect(result).to eq(b)
429
+ end
430
+ xml = parse_doc(instance)
431
+ expect(xml.at('DataTransfer/AdditionalOrderInfo')).to be_nil
432
+ end
433
+ end
434
+
435
+ describe '::V3' do
436
+ it 'add_data_digest creates DataDigest element with SignatureVersion' do
437
+ digest = OpenSSL::Digest.digest('SHA256', 'test')
438
+ instance = described_class::V3.new do |b|
439
+ b.add_data_digest('A006', digest)
440
+ end
441
+ xml = parse_doc(instance)
442
+ node = xml.at('DataTransfer/DataDigest')
443
+ expect(node).not_to be_nil
444
+ expect(node['SignatureVersion']).to eq('A006')
445
+ expect(node.text).to eq(Base64.strict_encode64(digest))
446
+ end
447
+
448
+ it 'add_additional_order_info creates AdditionalOrderInfo element' do
449
+ instance = described_class::V3.new do |b|
450
+ b.add_additional_order_info
451
+ end
452
+ xml = parse_doc(instance)
453
+ expect(xml.at('DataTransfer/AdditionalOrderInfo')).not_to be_nil
454
+ end
455
+ end
456
+
457
+ describe 'shared behavior (V2 and V3)' do
458
+ [Epics::Builders::DataTransferBuilder::V2, Epics::Builders::DataTransferBuilder::V3].each do |klass|
459
+ context klass.name do
460
+ it 'add_order_data compresses and Base64-encodes data' do
461
+ instance = klass.new do |b|
462
+ b.add_order_data('hello world')
463
+ end
464
+ xml = parse_doc(instance)
465
+ order_data = xml.at('DataTransfer/OrderData')
466
+ expect(order_data).not_to be_nil
467
+ decoded = Base64.strict_decode64(order_data.text)
468
+ decompressed = Zlib::Inflate.inflate(decoded)
469
+ expect(decompressed).to eq('hello world')
470
+ end
471
+
472
+ it 'add_order_data compresses, encrypts, and Base64-encodes with transaction key' do
473
+ crypt = Epics::Services::CryptService.new
474
+ transaction_key = OpenSSL::Cipher.new('aes-128-cbc').random_key
475
+ instance = klass.new do |b|
476
+ b.add_order_data('secret data', transaction_key)
477
+ end
478
+ xml = parse_doc(instance)
479
+ decoded = Base64.strict_decode64(xml.at('DataTransfer/OrderData').text)
480
+ decrypted = crypt.decrypt_by_key(transaction_key, decoded)
481
+ decompressed = Zlib::Inflate.inflate(decrypted)
482
+ expect(decompressed).to eq('secret data')
483
+ end
484
+
485
+ it 'add_signature_data compresses, encrypts, and Base64-encodes' do
486
+ crypt = Epics::Services::CryptService.new
487
+ transaction_key = OpenSSL::Cipher.new('aes-128-cbc').random_key
488
+ instance = klass.new do |b|
489
+ b.add_signature_data('<SignatureData>test</SignatureData>', transaction_key)
490
+ end
491
+ xml = parse_doc(instance)
492
+ node = xml.at('DataTransfer/SignatureData')
493
+ expect(node['authenticate']).to eq('true')
494
+ decoded = Base64.strict_decode64(node.text)
495
+ decrypted = crypt.decrypt_by_key(transaction_key, decoded)
496
+ decompressed = Zlib::Inflate.inflate(decrypted)
497
+ expect(decompressed).to eq('<SignatureData>test</SignatureData>')
498
+ end
499
+ end
500
+ end
501
+ end
502
+ end
503
+
504
+ describe Epics::Builders::DataEncryptionInfoBuilder do
505
+ let(:rsa_key) { OpenSSL::PKey::RSA.generate(2048) }
506
+ let(:rsa_algo) { Epics::SignatureAlgorithm::Rsa.new(rsa_key) }
507
+ let(:keyring) do
508
+ kr = double('keyring')
509
+ bank_enc = Epics::Signature.new(Epics::Signature::E_VERSION_2, rsa_algo)
510
+ allow(kr).to receive(:bank_encryption).and_return(bank_enc)
511
+ allow(kr).to receive(:version).and_return(Epics::Keyring::VERSION_25)
512
+ kr
513
+ end
514
+
515
+ it 'creates DataEncryptionInfo with authenticate=true' do
516
+ instance = described_class.new do |b|
517
+ b.add_encryption_pubkey_digest(keyring)
518
+ b.add_transaction_key(SecureRandom.random_bytes(16), keyring)
519
+ end
520
+ xml = parse_doc(instance)
521
+ root = xml.root
522
+ expect(root.name).to eq('DataEncryptionInfo')
523
+ expect(root['authenticate']).to eq('true')
524
+ end
525
+
526
+ it 'creates EncryptionPubKeyDigest with version and algorithm' do
527
+ instance = described_class.new do |b|
528
+ b.add_encryption_pubkey_digest(keyring)
529
+ end
530
+ xml = parse_doc(instance)
531
+ node = xml.at('DataEncryptionInfo/EncryptionPubKeyDigest')
532
+ expect(node['Version']).to eq('E002')
533
+ expect(node['Algorithm']).to eq('http://www.w3.org/2001/04/xmlenc#sha256')
534
+ expect(node.text).not_to be_empty
535
+ end
536
+
537
+ it 'creates TransactionKey that can be decrypted with private key' do
538
+ transaction_key = SecureRandom.random_bytes(16)
539
+ instance = described_class.new do |b|
540
+ b.add_transaction_key(transaction_key, keyring)
541
+ end
542
+ xml = parse_doc(instance)
543
+ encrypted_b64 = xml.at('DataEncryptionInfo/TransactionKey').text
544
+ decrypted = rsa_key.private_decrypt(Base64.strict_decode64(encrypted_b64))
545
+ expect(decrypted).to eq(transaction_key)
546
+ end
547
+ end
548
+
549
+ describe Epics::Builders::HeaderBuilder do
550
+ describe '::V2' do
551
+ it 'creates header with authenticate=true' do
552
+ instance = Epics::Builders::HeaderBuilder::V2.new do |b|
553
+ b.add_mutable do |m|
554
+ m.add_transaction_phase 'Initialisation'
555
+ end
556
+ end
557
+ xml = parse_doc(instance)
558
+ expect(xml.root.name).to eq('header')
559
+ expect(xml.root['authenticate']).to eq('true')
560
+ expect(xml.at('header/mutable/TransactionPhase').text).to eq('Initialisation')
561
+ end
562
+ end
563
+
564
+ describe '::V3' do
565
+ it 'creates header with authenticate=true using V3 static builder' do
566
+ instance = Epics::Builders::HeaderBuilder::V3.new do |b|
567
+ b.add_static do |s|
568
+ s.add_host_id 'TESTHOST'
569
+ end
570
+ end
571
+ xml = parse_doc(instance)
572
+ expect(xml.root.name).to eq('header')
573
+ expect(xml.at('header/static/HostID').text).to eq('TESTHOST')
574
+ end
575
+ end
576
+ end
577
+
578
+ describe Epics::Builders::BodyBuilder do
579
+ describe '::V2' do
580
+ it 'creates body element' do
581
+ instance = Epics::Builders::BodyBuilder::V2.new { |b| }
582
+ xml = parse_doc(instance)
583
+ expect(xml.root.name).to eq('body')
584
+ end
585
+
586
+ it 'delegates add_transfer_receipt to TransferReceiptBuilder' do
587
+ instance = Epics::Builders::BodyBuilder::V2.new do |b|
588
+ b.add_transfer_receipt do |r|
589
+ r.add_receipt_code 0
590
+ end
591
+ end
592
+ xml = parse_doc(instance)
593
+ expect(xml.at('body/TransferReceipt/ReceiptCode').text).to eq('0')
594
+ expect(xml.at('body/TransferReceipt')['authenticate']).to eq('true')
595
+ end
596
+ end
597
+
598
+ describe '::V3' do
599
+ it 'creates body element' do
600
+ instance = Epics::Builders::BodyBuilder::V3.new { |b| }
601
+ xml = parse_doc(instance)
602
+ expect(xml.root.name).to eq('body')
603
+ end
604
+
605
+ it 'delegates add_data_transfer to DataTransferBuilder::V3' do
606
+ instance = Epics::Builders::BodyBuilder::V3.new do |b|
607
+ b.add_data_transfer do |dt|
608
+ dt.add_additional_order_info
609
+ end
610
+ end
611
+ xml = parse_doc(instance)
612
+ expect(xml.at('body/DataTransfer/AdditionalOrderInfo')).not_to be_nil
613
+ end
614
+ end
615
+ end
616
+ end