defra_ruby_mocks 1.1.0 → 1.5.0

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 (35) hide show
  1. checksums.yaml +5 -5
  2. data/README.md +52 -2
  3. data/Rakefile +0 -2
  4. data/app/controllers/defra_ruby_mocks/company_controller.rb +1 -1
  5. data/app/controllers/defra_ruby_mocks/worldpay_controller.rb +33 -12
  6. data/app/services/defra_ruby_mocks/{worldpay_request_service.rb → worldpay_payment_service.rb} +3 -9
  7. data/app/services/defra_ruby_mocks/worldpay_refund_service.rb +37 -0
  8. data/app/services/defra_ruby_mocks/worldpay_request_handler_service.rb +40 -0
  9. data/app/services/defra_ruby_mocks/worldpay_resource_service.rb +55 -0
  10. data/app/services/defra_ruby_mocks/worldpay_response_service.rb +71 -52
  11. data/app/views/defra_ruby_mocks/worldpay/payment_request.xml.erb +4 -0
  12. data/app/views/defra_ruby_mocks/worldpay/refund_request.xml.erb +4 -0
  13. data/app/views/defra_ruby_mocks/worldpay/stuck.html.erb +37 -0
  14. data/lib/defra_ruby_mocks/engine.rb +2 -1
  15. data/lib/defra_ruby_mocks/missing_resource_error.rb +9 -0
  16. data/lib/defra_ruby_mocks/unrecognised_worldpay_request_error.rb +5 -0
  17. data/lib/defra_ruby_mocks/version.rb +1 -1
  18. data/spec/dummy/log/development.log +180 -0
  19. data/spec/dummy/log/test.log +1287 -323
  20. data/spec/examples.txt +99 -56
  21. data/spec/fixtures/{worldpay_request_invalid.xml → payment_request_invalid.xml} +0 -0
  22. data/spec/fixtures/{worldpay_request_valid.xml → payment_request_valid.xml} +0 -0
  23. data/spec/fixtures/refund_request_invalid.xml +6 -0
  24. data/spec/fixtures/refund_request_valid.xml +11 -0
  25. data/spec/fixtures/unrecognised_request.xml +6 -0
  26. data/spec/requests/worldpay_spec.rb +87 -26
  27. data/spec/services/{worldpay_request_service_spec.rb → worldpay_payment_service_spec.rb} +25 -22
  28. data/spec/services/worldpay_refund_service_spec.rb +68 -0
  29. data/spec/services/worldpay_request_handler_service_spec.rb +79 -0
  30. data/spec/services/worldpay_resource_service_spec.rb +112 -0
  31. data/spec/services/worldpay_response_service_spec.rb +185 -58
  32. data/spec/support/helpers/xml_matchers.rb +19 -0
  33. metadata +50 -17
  34. data/app/views/defra_ruby_mocks/worldpay/payments_service.xml.erb +0 -4
  35. data/lib/defra_ruby_mocks/missing_registration_error.rb +0 -9
@@ -0,0 +1,79 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "rails_helper"
4
+
5
+ module DefraRubyMocks
6
+ RSpec.describe WorldpayRequestHandlerService do
7
+ describe ".run" do
8
+ context "when a request is made" do
9
+
10
+ let(:merchant_code) { "MERCHME" }
11
+ let(:args) { { merchant_code: merchant_code, xml: xml } }
12
+
13
+ context "and it's for a payment" do
14
+ before do
15
+ allow_any_instance_of(WorldpayPaymentService).to receive(:generate_id) { order_id }
16
+ end
17
+
18
+ let(:xml) { Nokogiri::XML(File.read("spec/fixtures/payment_request_valid.xml")) }
19
+ let(:order_id) { "1234567890" }
20
+ let(:request_type) { { request_type: :payment } }
21
+ let(:response_values) do
22
+ {
23
+ merchant_code: merchant_code,
24
+ order_code: "1577726052",
25
+ id: order_id,
26
+ url: "http://example.com"
27
+ }
28
+ end
29
+
30
+ it "correctly determines the request service to use" do
31
+ expect(WorldpayPaymentService).to receive(:run).with(args) { response_values }
32
+
33
+ described_class.run(xml)
34
+ end
35
+
36
+ it "returns the values the controller needs to handle the request" do
37
+ expect(WorldpayPaymentService).to receive(:run).with(args) { response_values }
38
+
39
+ expect(described_class.run(xml)).to eq(request_type.merge(response_values))
40
+ end
41
+ end
42
+
43
+ context "and it's for a refund" do
44
+ let(:xml) { Nokogiri::XML(File.read("spec/fixtures/refund_request_valid.xml")) }
45
+ let(:request_type) { { request_type: :refund } }
46
+ let(:response_values) do
47
+ {
48
+ merchant_code: merchant_code,
49
+ order_code: "1579644835",
50
+ refund_value: "2500",
51
+ currency_code: "GBP",
52
+ exponent: "2"
53
+ }
54
+ end
55
+
56
+ it "correctly determines the request service to use" do
57
+ expect(WorldpayRefundService).to receive(:run).with(args) { response_values }
58
+
59
+ described_class.run(xml)
60
+ end
61
+
62
+ it "returns the values the controller needs to handle the request" do
63
+ expect(WorldpayRefundService).to receive(:run).with(args) { response_values }
64
+
65
+ expect(described_class.run(xml)).to eq(request_type.merge(response_values))
66
+ end
67
+ end
68
+
69
+ context "but it's not recognised" do
70
+ let(:xml) { Nokogiri::XML(File.read("spec/fixtures/unrecognised_request.xml")) }
71
+
72
+ it "raises a 'UnrecognisedWorldpayRequestError'" do
73
+ expect { described_class.run(xml) }.to raise_error UnrecognisedWorldpayRequestError
74
+ end
75
+ end
76
+ end
77
+ end
78
+ end
79
+ end
@@ -0,0 +1,112 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "rails_helper"
4
+
5
+ module DefraRubyMocks
6
+ RSpec.describe WorldpayResourceService do
7
+ before(:each) do
8
+ allow(::WasteCarriersEngine::TransientRegistration).to receive(:where) { transient_relation }
9
+ allow(::WasteCarriersEngine::Registration).to receive(:where) { registration_relation }
10
+ end
11
+
12
+ let(:reference) { "12345" }
13
+ let(:company_name) { "Pay for the thing" }
14
+
15
+ let(:resource) { double(:resource, finance_details: finance_details, company_name: company_name) }
16
+ let(:finance_details) { double(:finance_details, orders: orders) }
17
+ let(:orders) { double(:orders, order_by: sorted_orders) }
18
+ let(:sorted_orders) { double(:sorted_orders, first: order) }
19
+ let(:order) { double(:order) }
20
+
21
+ let(:args) { { reference: reference } }
22
+
23
+ describe ".run" do
24
+
25
+ context "when the resource is a TransientRegistration" do
26
+ let(:transient_relation) { double(:relation, first: resource) }
27
+
28
+ it "will only search transient registrations" do
29
+ described_class.run(args)
30
+
31
+ expect(::WasteCarriersEngine::TransientRegistration).to have_received(:where).with(token: reference)
32
+
33
+ expect(::WasteCarriersEngine::Registration).not_to have_received(:where).with(reg_uuid: reference)
34
+ end
35
+
36
+ it "returns an object with the matching resource" do
37
+ expect(described_class.run(args).resource).to eq(resource)
38
+ end
39
+
40
+ it "returns an object with the expected order" do
41
+ expect(described_class.run(args).order).to eq(order)
42
+ end
43
+
44
+ it "returns an object with the expected company name" do
45
+ expect(described_class.run(args).company_name).to eq(company_name.downcase)
46
+ end
47
+ end
48
+
49
+ context "when the resource is a Registration" do
50
+ let(:transient_relation) { double(:relation, first: nil) }
51
+ let(:registration_relation) { double(:relation, first: resource) }
52
+
53
+ it "will search transient registrations first, then registrations" do
54
+ described_class.run(args)
55
+
56
+ expect(::WasteCarriersEngine::TransientRegistration).to have_received(:where).with(token: reference)
57
+
58
+ expect(::WasteCarriersEngine::Registration).to have_received(:where).with(reg_uuid: reference)
59
+ end
60
+
61
+ it "returns an object with the matching resource" do
62
+ expect(described_class.run(args).resource).to eq(resource)
63
+ end
64
+
65
+ it "returns an object with the expected order" do
66
+ expect(described_class.run(args).order).to eq(order)
67
+ end
68
+
69
+ it "returns an object with the expected company name" do
70
+ expect(described_class.run(args).company_name).to eq(company_name.downcase)
71
+ end
72
+ end
73
+
74
+ context "when the resource is a OrderCopyCardsRegistration" do
75
+ before do
76
+ # Because we do not copy the company name to
77
+ # `OrderCopyCardsRegistration` instances when we create them in WCR
78
+ # we need to locate the orignal registration they are based on. We
79
+ # determine in the class if the 'resource' is an instance of one by
80
+ # comparing the result of resource.class.to_s to
81
+ # "WasteCarriersEngine::OrderCopyCardsRegistration". The problem is
82
+ # when testing 'resource' is actually an instance of
83
+ # `RSpec::Mocks::Double`! So we subvert the call to class on
84
+ # RSpec::Mocks::Double to return "WasteCarriersEngine::OrderCopyCardsRegistration"
85
+ # just in this spec. We can then test that the service does indeed
86
+ # locate the original registration for a company name
87
+ allow_any_instance_of(RSpec::Mocks::Double).to receive(:class)
88
+ .and_return("WasteCarriersEngine::OrderCopyCardsRegistration")
89
+ end
90
+
91
+ let(:copy_card_resource) { double(:resource, finance_details: finance_details, reg_identifier: "CBDU123") }
92
+ let(:transient_relation) { double(:relation, first: copy_card_resource) }
93
+ let(:registration_relation) { double(:relation, first: resource) }
94
+
95
+ it "locates the original registration to grab the company name" do
96
+ expect(described_class.run(args).company_name).to eq(company_name.downcase)
97
+
98
+ expect(::WasteCarriersEngine::Registration).to have_received(:where).with(reg_identifier: "CBDU123")
99
+ end
100
+ end
101
+
102
+ context "when the resource does not exist" do
103
+ let(:transient_relation) { double(:relation, first: nil) }
104
+ let(:registration_relation) { double(:relation, first: nil) }
105
+
106
+ it "causes an error" do
107
+ expect { described_class.run(args) }.to raise_error MissingResourceError
108
+ end
109
+ end
110
+ end
111
+ end
112
+ end
@@ -3,6 +3,7 @@
3
3
  require "rails_helper"
4
4
 
5
5
  module DefraRubyMocks
6
+
6
7
  RSpec.describe WorldpayResponseService do
7
8
  before(:each) do
8
9
  Helpers::Configuration.prep_for_tests
@@ -12,10 +13,11 @@ module DefraRubyMocks
12
13
  config.worldpay_mac_secret = mac_secret
13
14
  end
14
15
 
15
- allow(::WasteCarriersEngine::TransientRegistration).to receive(:where) { relation }
16
- allow(::WasteCarriersEngine::Registration).to receive(:where) { relation }
16
+ allow(WorldpayResourceService).to receive(:run) { resource }
17
17
  end
18
18
 
19
+ let(:resource) { double(:resource, order: order, company_name: company_name.downcase) }
20
+
19
21
  let(:admin_code) { "admincode1" }
20
22
  let(:merchant_code) { "merchantcode1" }
21
23
  let(:mac_secret) { "mac1" }
@@ -23,29 +25,17 @@ module DefraRubyMocks
23
25
  let(:order_code) { "54321" }
24
26
  let(:order_key) { "#{admin_code}^#{merchant_code}^#{order_code}" }
25
27
  let(:order_value) { 105_00 }
28
+ let(:payment_status) { :AUTHORISED }
29
+ let(:company_name) { "Pay for the thing" }
26
30
 
27
- let(:registration) { double(:registration, finance_details: finance_details) }
28
- let(:finance_details) { double(:finance_details, orders: orders) }
29
- let(:orders) { double(:orders, order_by: sorted_orders) }
30
- let(:sorted_orders) { double(:sorted_orders, first: order) }
31
31
  let(:order) { double(:order, order_code: order_code, total_amount: order_value) }
32
32
 
33
- let(:mac) do
34
- data = [
35
- order_key,
36
- order_value,
37
- "GBP",
38
- "AUTHORISED",
39
- mac_secret
40
- ]
41
-
42
- Digest::MD5.hexdigest(data.join).to_s
43
- end
33
+ let(:mac) { Digest::MD5.hexdigest(mac_data.join).to_s }
44
34
 
45
35
  let(:query_string) do
46
36
  [
47
37
  "orderKey=#{order_key}",
48
- "paymentStatus=AUTHORISED",
38
+ "paymentStatus=#{payment_status}",
49
39
  "paymentAmount=#{order_value}",
50
40
  "paymentCurrency=GBP",
51
41
  "mac=#{mac}",
@@ -53,91 +43,228 @@ module DefraRubyMocks
53
43
  ].join("&")
54
44
  end
55
45
 
46
+ let(:args) do
47
+ {
48
+ success_url: success_url,
49
+ failure_url: failure_url,
50
+ pending_url: pending_url,
51
+ cancel_url: cancel_url,
52
+ error_url: error_url
53
+ }
54
+ end
55
+
56
56
  describe ".run" do
57
57
  context "when the request comes from the waste-carriers-front-office" do
58
58
  let(:success_url) { "http://example.com/fo/#{reference}/worldpay/success" }
59
+ let(:failure_url) { "http://example.com/fo/#{reference}/worldpay/failure" }
60
+ let(:pending_url) { "http://example.com/fo/#{reference}/worldpay/pending" }
61
+ let(:cancel_url) { "http://example.com/fo/#{reference}/worldpay/cancel" }
62
+ let(:error_url) { "http://example.com/fo/#{reference}/worldpay/error" }
59
63
 
60
64
  context "and is valid" do
61
65
  let(:relation) { double(:relation, first: registration) }
66
+ let(:mac_data) { [order_key, order_value, "GBP", payment_status, mac_secret] }
62
67
 
63
68
  it "can extract the reference from the `success_url`" do
64
- described_class.run(success_url)
65
-
66
- expect(::WasteCarriersEngine::TransientRegistration).to have_received(:where).with(token: reference)
69
+ expect(described_class.run(args).reference).to eq(reference)
67
70
  end
68
71
 
69
72
  it "can generate a valid order key" do
70
- params = parse_for_params(described_class.run(success_url))
73
+ expect(described_class.run(args).order_key).to eq(order_key)
74
+ end
75
+
76
+ context "and is for a successful payment" do
77
+ it "can generate a valid mac" do
78
+ puts "Test data #{mac_data.join}"
79
+ expect(described_class.run(args).mac).to eq(mac)
80
+ end
81
+
82
+ it "returns a url in the expected format" do
83
+ expected_response_url = "#{success_url}?#{query_string}"
84
+
85
+ expect(described_class.run(args).url).to eq(expected_response_url)
86
+ end
87
+ end
71
88
 
72
- expect(params["orderKey"]).to eq(order_key)
89
+ context "and is for a rejected payment" do
90
+ let(:payment_status) { :REFUSED }
91
+ let(:company_name) { "Reject for the thing" }
92
+
93
+ it "can generate a valid mac" do
94
+ expect(described_class.run(args).mac).to eq(mac)
95
+ end
96
+
97
+ it "returns a url in the expected format" do
98
+ expected_response_url = "#{failure_url}?#{query_string}"
99
+
100
+ expect(described_class.run(args).url).to eq(expected_response_url)
101
+ end
102
+ end
103
+
104
+ context "and is for a stuck payment" do
105
+ let(:payment_status) { :STUCK }
106
+ let(:company_name) { "Give me a stuck thing" }
107
+
108
+ it "can generate a valid mac" do
109
+ expect(described_class.run(args).mac).to eq(mac)
110
+ end
111
+
112
+ it "returns a status of :STUCK" do
113
+ expect(described_class.run(args).status).to eq(:STUCK)
114
+ end
73
115
  end
74
116
 
75
- it "can generate a valid mac" do
76
- params = parse_for_params(described_class.run(success_url))
117
+ context "and is for a pending payment" do
118
+ let(:payment_status) { :SENT_FOR_AUTHORISATION }
119
+ let(:company_name) { "Pending for the thing" }
120
+
121
+ it "can generate a valid mac" do
122
+ expect(described_class.run(args).mac).to eq(mac)
123
+ end
124
+
125
+ it "returns a url in the expected format" do
126
+ expected_response_url = "#{pending_url}?#{query_string}"
77
127
 
78
- expect(params["mac"]).to eq(mac)
128
+ expect(described_class.run(args).url).to eq(expected_response_url)
129
+ end
79
130
  end
80
131
 
81
- it "returns a url in the expected format" do
82
- expected_response = "#{success_url}?#{query_string}"
132
+ context "and is for a cancelled payment" do
133
+ let(:payment_status) { :CANCELLED }
134
+ let(:company_name) { "Cancel the thing" }
135
+ let(:mac_data) { [order_key, order_value, "GBP", mac_secret] }
83
136
 
84
- expect(described_class.run(success_url)).to eq(expected_response)
137
+ it "can generate a valid mac" do
138
+ expect(described_class.run(args).mac).to eq(mac)
139
+ end
140
+
141
+ it "returns a url in the expected format" do
142
+ expected_response_url = "#{cancel_url}?#{query_string}"
143
+
144
+ expect(described_class.run(args).url).to eq(expected_response_url)
145
+ end
85
146
  end
86
- end
87
147
 
88
- context "but the registration does not exist" do
89
- let(:relation) { double(:relation, first: nil) }
148
+ context "and is for an errored payment" do
149
+ let(:payment_status) { :ERROR }
150
+ let(:company_name) { "Error the thing" }
90
151
 
91
- it "causes an error" do
92
- expect { described_class.run(success_url) }.to raise_error MissingRegistrationError
152
+ it "can generate a valid mac" do
153
+ expect(described_class.run(args).mac).to eq(mac)
154
+ end
155
+
156
+ it "returns a url in the expected format" do
157
+ expected_response_url = "#{error_url}?#{query_string}"
158
+
159
+ expect(described_class.run(args).url).to eq(expected_response_url)
160
+ end
93
161
  end
94
162
  end
95
163
  end
96
164
 
97
165
  context "when the request comes from the waste-carriers-frontend" do
98
- before do
99
- # The service will search transient registrations for a match first
100
- # before then searching for the registration. Hence we need to stub
101
- # `locate_transient_registration()` to allow the service to then
102
- # call `locate_registration()`
103
- allow_any_instance_of(described_class).to receive(:locate_transient_registration).and_return(nil)
104
- end
105
-
106
166
  let(:success_url) { "http://example.com/your-registration/#{reference}/worldpay/success/54321/NEWREG?locale=en" }
167
+ let(:failure_url) { "http://example.com/your-registration/#{reference}/worldpay/failure/54321/NEWREG?locale=en" }
168
+ let(:pending_url) { "http://example.com/your-registration/#{reference}/worldpay/pending/54321/NEWREG?locale=en" }
169
+ let(:cancel_url) { "http://example.com/your-registration/#{reference}/worldpay/cancel/54321/NEWREG?locale=en" }
170
+ let(:error_url) { "http://example.com/your-registration/#{reference}/worldpay/error/54321/NEWREG?locale=en" }
107
171
 
108
172
  context "and is valid" do
109
173
  let(:relation) { double(:relation, first: registration) }
174
+ let(:mac_data) { [order_key, order_value, "GBP", payment_status, mac_secret] }
110
175
 
111
176
  it "can extract the reference from the `success_url`" do
112
- described_class.run(success_url)
113
-
114
- expect(::WasteCarriersEngine::Registration).to have_received(:where).with(reg_uuid: reference)
177
+ expect(described_class.run(args).reference).to eq(reference)
115
178
  end
116
179
 
117
180
  it "can generate a valid order key" do
118
- params = parse_for_params(described_class.run(success_url))
181
+ expect(described_class.run(args).order_key).to eq(order_key)
182
+ end
119
183
 
120
- expect(params["orderKey"]).to eq(order_key)
184
+ context "and is for a successful payment" do
185
+ it "can generate a valid mac" do
186
+ expect(described_class.run(args).mac).to eq(mac)
187
+ end
188
+
189
+ it "returns a url in the expected format" do
190
+ expected_response_url = "#{success_url}&#{query_string}"
191
+
192
+ expect(described_class.run(args).url).to eq(expected_response_url)
193
+ end
121
194
  end
122
195
 
123
- it "can generate a valid mac" do
124
- params = parse_for_params(described_class.run(success_url))
196
+ context "and is for a rejected payment" do
197
+ let(:payment_status) { :REFUSED }
198
+ let(:company_name) { "Reject for the thing" }
199
+
200
+ it "can generate a valid mac" do
201
+ expect(described_class.run(args).mac).to eq(mac)
202
+ end
125
203
 
126
- expect(params["mac"]).to eq(mac)
204
+ it "returns a url in the expected format" do
205
+ expected_response_url = "#{failure_url}&#{query_string}"
206
+
207
+ expect(described_class.run(args).url).to eq(expected_response_url)
208
+ end
127
209
  end
128
210
 
129
- it "returns a url in the expected format" do
130
- expected_response = "#{success_url}&#{query_string}"
211
+ context "and is for a stuck payment" do
212
+ let(:payment_status) { :STUCK }
213
+ let(:company_name) { "Give me a stuck thing" }
214
+
215
+ it "can generate a valid mac" do
216
+ expect(described_class.run(args).mac).to eq(mac)
217
+ end
131
218
 
132
- expect(described_class.run(success_url)).to eq(expected_response)
219
+ it "returns a status of :STUCK" do
220
+ expect(described_class.run(args).status).to eq(:STUCK)
221
+ end
133
222
  end
134
- end
135
223
 
136
- context "but the registration does not exist" do
137
- let(:relation) { double(:relation, first: nil) }
224
+ context "and is for a pending payment" do
225
+ let(:payment_status) { :SENT_FOR_AUTHORISATION }
226
+ let(:company_name) { "Pending for the thing" }
227
+
228
+ it "can generate a valid mac" do
229
+ expect(described_class.run(args).mac).to eq(mac)
230
+ end
231
+
232
+ it "returns a url in the expected format" do
233
+ expected_response_url = "#{pending_url}&#{query_string}"
234
+
235
+ expect(described_class.run(args).url).to eq(expected_response_url)
236
+ end
237
+ end
238
+
239
+ context "and is for a cancelled payment" do
240
+ let(:payment_status) { :CANCELLED }
241
+ let(:company_name) { "Cancel the thing" }
242
+ let(:mac_data) { [order_key, order_value, "GBP", mac_secret] }
243
+
244
+ it "can generate a valid mac" do
245
+ expect(described_class.run(args).mac).to eq(mac)
246
+ end
247
+
248
+ it "returns a url in the expected format" do
249
+ expected_response_url = "#{cancel_url}&#{query_string}"
250
+
251
+ expect(described_class.run(args).url).to eq(expected_response_url)
252
+ end
253
+ end
254
+
255
+ context "and is for an errored payment" do
256
+ let(:payment_status) { :ERROR }
257
+ let(:company_name) { "Error the thing" }
258
+
259
+ it "can generate a valid mac" do
260
+ expect(described_class.run(args).mac).to eq(mac)
261
+ end
262
+
263
+ it "returns a url in the expected format" do
264
+ expected_response_url = "#{error_url}&#{query_string}"
138
265
 
139
- it "causes an error" do
140
- expect { described_class.run(success_url) }.to raise_error MissingRegistrationError
266
+ expect(described_class.run(args).url).to eq(expected_response_url)
267
+ end
141
268
  end
142
269
  end
143
270
  end