effective_qb_sync 1.0.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 (92) hide show
  1. checksums.yaml +7 -0
  2. data/MIT-LICENSE +20 -0
  3. data/README.md +94 -0
  4. data/Rakefile +21 -0
  5. data/app/controllers/admin/qb_syncs_controller.rb +60 -0
  6. data/app/controllers/effective/qb_sync_controller.rb +40 -0
  7. data/app/models/effective/access_denied.rb +17 -0
  8. data/app/models/effective/datatables/qb_syncs.rb +30 -0
  9. data/app/models/effective/qb_log.rb +13 -0
  10. data/app/models/effective/qb_machine.rb +281 -0
  11. data/app/models/effective/qb_order_item.rb +13 -0
  12. data/app/models/effective/qb_order_items_form.rb +55 -0
  13. data/app/models/effective/qb_request.rb +262 -0
  14. data/app/models/effective/qb_ticket.rb +55 -0
  15. data/app/models/effective/qbwc_supervisor.rb +89 -0
  16. data/app/views/admin/qb_syncs/_actions.html.haml +2 -0
  17. data/app/views/admin/qb_syncs/_qb_item_names.html.haml +9 -0
  18. data/app/views/admin/qb_syncs/index.html.haml +24 -0
  19. data/app/views/admin/qb_syncs/instructions.html.haml +136 -0
  20. data/app/views/admin/qb_syncs/show.html.haml +52 -0
  21. data/app/views/effective/orders_mailer/qb_sync_error.html.haml +56 -0
  22. data/app/views/effective/qb_sync/authenticate.erb +12 -0
  23. data/app/views/effective/qb_sync/clientVersion.erb +8 -0
  24. data/app/views/effective/qb_sync/closeConnection.erb +8 -0
  25. data/app/views/effective/qb_sync/connectionError.erb +9 -0
  26. data/app/views/effective/qb_sync/getLastError.erb +9 -0
  27. data/app/views/effective/qb_sync/receiveResponseXML.erb +8 -0
  28. data/app/views/effective/qb_sync/sendRequestXML.erb +8 -0
  29. data/app/views/effective/qb_sync/serverVersion.erb +8 -0
  30. data/app/views/effective/qb_web_connector/quickbooks.qwc.erb +12 -0
  31. data/config/routes.rb +16 -0
  32. data/db/migrate/01_create_effective_qb_sync.rb.erb +68 -0
  33. data/lib/effective_qb_sync/engine.rb +42 -0
  34. data/lib/effective_qb_sync/version.rb +3 -0
  35. data/lib/effective_qb_sync.rb +42 -0
  36. data/lib/generators/effective_qb_sync/install_generator.rb +42 -0
  37. data/lib/generators/templates/effective_qb_sync.rb +61 -0
  38. data/lib/generators/templates/effective_qb_sync_mailer_preview.rb +39 -0
  39. data/spec/dummy/README.rdoc +8 -0
  40. data/spec/dummy/Rakefile +6 -0
  41. data/spec/dummy/app/assets/javascripts/application.js +13 -0
  42. data/spec/dummy/app/assets/stylesheets/application.css +15 -0
  43. data/spec/dummy/app/controllers/application_controller.rb +5 -0
  44. data/spec/dummy/app/helpers/application_helper.rb +2 -0
  45. data/spec/dummy/app/models/product.rb +14 -0
  46. data/spec/dummy/app/models/product_with_float_price.rb +13 -0
  47. data/spec/dummy/app/models/user.rb +14 -0
  48. data/spec/dummy/app/views/layouts/application.html.erb +14 -0
  49. data/spec/dummy/bin/bundle +3 -0
  50. data/spec/dummy/bin/rails +4 -0
  51. data/spec/dummy/bin/rake +4 -0
  52. data/spec/dummy/config/application.rb +32 -0
  53. data/spec/dummy/config/boot.rb +5 -0
  54. data/spec/dummy/config/database.yml +25 -0
  55. data/spec/dummy/config/environment.rb +5 -0
  56. data/spec/dummy/config/environments/development.rb +37 -0
  57. data/spec/dummy/config/environments/production.rb +80 -0
  58. data/spec/dummy/config/environments/test.rb +36 -0
  59. data/spec/dummy/config/initializers/backtrace_silencers.rb +7 -0
  60. data/spec/dummy/config/initializers/cookies_serializer.rb +3 -0
  61. data/spec/dummy/config/initializers/devise.rb +254 -0
  62. data/spec/dummy/config/initializers/effective_addresses.rb +15 -0
  63. data/spec/dummy/config/initializers/effective_orders.rb +154 -0
  64. data/spec/dummy/config/initializers/effective_qb_sync.rb +41 -0
  65. data/spec/dummy/config/initializers/filter_parameter_logging.rb +4 -0
  66. data/spec/dummy/config/initializers/inflections.rb +16 -0
  67. data/spec/dummy/config/initializers/mime_types.rb +4 -0
  68. data/spec/dummy/config/initializers/session_store.rb +3 -0
  69. data/spec/dummy/config/initializers/simple_form.rb +189 -0
  70. data/spec/dummy/config/initializers/wrap_parameters.rb +14 -0
  71. data/spec/dummy/config/locales/en.yml +23 -0
  72. data/spec/dummy/config/routes.rb +3 -0
  73. data/spec/dummy/config/secrets.yml +22 -0
  74. data/spec/dummy/config.ru +4 -0
  75. data/spec/dummy/db/schema.rb +208 -0
  76. data/spec/dummy/db/test.sqlite3 +0 -0
  77. data/spec/dummy/log/development.log +90 -0
  78. data/spec/dummy/log/test.log +1 -0
  79. data/spec/dummy/public/404.html +67 -0
  80. data/spec/dummy/public/422.html +67 -0
  81. data/spec/dummy/public/500.html +66 -0
  82. data/spec/dummy/public/favicon.ico +0 -0
  83. data/spec/fixtures/qbxml_response_error.xml +6 -0
  84. data/spec/fixtures/qbxml_response_success.xml +621 -0
  85. data/spec/models/acts_as_purchasable_spec.rb +131 -0
  86. data/spec/models/factories_spec.rb +32 -0
  87. data/spec/models/qb_machine_spec.rb +554 -0
  88. data/spec/models/qb_request_spec.rb +327 -0
  89. data/spec/models/qb_ticket_spec.rb +62 -0
  90. data/spec/spec_helper.rb +45 -0
  91. data/spec/support/factories.rb +97 -0
  92. metadata +397 -0
@@ -0,0 +1,131 @@
1
+ require 'spec_helper'
2
+
3
+ # # Attributes
4
+ describe Product do
5
+ let(:user) { FactoryGirl.create(:user) }
6
+ let(:order) { FactoryGirl.create(:order) }
7
+ let(:product) { order.order_items.first.purchasable }
8
+ let(:product_with_float_price) { FactoryGirl.create(:product_with_float_price) }
9
+
10
+ describe 'assumptions' do
11
+ it 'should be effectively purchasable' do
12
+ product.kind_of?(ActsAsPurchasable).should eq true
13
+ end
14
+ end
15
+
16
+ describe 'purchased' do
17
+ it 'is purchased? when in a purchased Order' do
18
+ order.purchase!
19
+
20
+ product.purchased?.should eq true
21
+ product.purchased_orders.include?(order).should eq true
22
+ end
23
+
24
+ it 'is purchased? in the after_purchase callback' do
25
+ instance_order = nil
26
+ instance_product = nil
27
+ instance_purchased = nil
28
+
29
+ Product.instance_eval do
30
+ after_purchase do |order, order_item|
31
+ if defined?(:instance_order)
32
+ instance_order = order
33
+ instance_product = self
34
+ instance_purchased = self.purchased?
35
+ end
36
+ end
37
+ end
38
+
39
+ order.purchase!
40
+
41
+ instance_order.purchased?.should eq true
42
+ instance_product.purchased?.should eq true
43
+ instance_purchased.should eq true
44
+ end
45
+
46
+ it 'is returned by the purchased scopes' do
47
+ order.purchase!
48
+
49
+ Product.purchased.to_a.include?(product).should eq true
50
+ Product.purchased_by(order.user).to_a.include?(product).should eq true
51
+
52
+ Product.sold.to_a.include?(product).should eq true
53
+
54
+ Product.not_purchased.to_a.include?(product).should eq false
55
+ end
56
+ end
57
+
58
+ describe 'float prices' do
59
+ it 'should automatically convert float prices to integer' do
60
+ product_with_float_price.price = 20.00
61
+ product_with_float_price.tax_exempt = true
62
+
63
+ order = Effective::Order.new(product_with_float_price, user: user)
64
+ order.billing_address = FactoryGirl.create(:address, state_code: 'AB')
65
+
66
+ order.subtotal.should eq 2000
67
+ order.tax.should eq 0
68
+ order.total.should eq 2000
69
+ end
70
+
71
+ it 'should automatically convert tax floats to integers' do
72
+ product_with_float_price.price = 20.00
73
+ product_with_float_price.tax_exempt = false
74
+
75
+ order = Effective::Order.new(product_with_float_price, user: user)
76
+ order.billing_address = FactoryGirl.create(:address, state_code: 'AB')
77
+
78
+ order.subtotal.should eq 2000
79
+ order.tax.should eq 100
80
+ order.total.should eq 2100
81
+ end
82
+ end
83
+
84
+ describe 'price=' do
85
+ it 'should accept an integer price' do
86
+ product = Product.new()
87
+ product.price = 1250
88
+
89
+ product.price.should eq 1250
90
+ end
91
+
92
+ it 'should convert a String that looks like an Integer' do
93
+ product = Product.new()
94
+ product.price = '1250'
95
+
96
+ product.price.should eq 1250
97
+ end
98
+
99
+ it 'should convert a String that looks like a Float' do
100
+ product = Product.new()
101
+ product.price = '12.50'
102
+
103
+ product.price.should eq 1250
104
+ end
105
+
106
+ it 'should convert from a Float' do
107
+ product = Product.new()
108
+ product.price = 12.50
109
+ product.price.should eq 1250
110
+
111
+ product.price = Float(12.50)
112
+ product.price.should eq 1250
113
+ end
114
+
115
+ it 'should convert from a BigDecimal' do
116
+ product = Product.new()
117
+ product.price = BigDecimal.new(12.5, 4)
118
+
119
+ product.price.should eq 1250
120
+ end
121
+
122
+ it 'should treat nil as a zero' do
123
+ product = Product.new()
124
+ product.price = nil
125
+
126
+ product.price.should eq 0
127
+ end
128
+
129
+ end
130
+
131
+ end
@@ -0,0 +1,32 @@
1
+ require 'spec_helper'
2
+
3
+ # Attributes
4
+ describe 'Factories' do
5
+ let(:factories) { [:user, :address, :product, :cart, :order_item, :order, :purchased_order, :declined_order] }
6
+
7
+ it 'should have all valid factories' do
8
+ factories.each do |factory|
9
+ obj = FactoryGirl.create(factory)
10
+
11
+ puts "Invalid factory #{factory}: #{obj.errors.inspect}" unless obj.valid?
12
+
13
+ obj.valid?.should eq true
14
+ end
15
+ end
16
+
17
+ it 'should have created an Order with a billing_address and shipping_address' do
18
+ order = FactoryGirl.create(:order)
19
+
20
+ order.billing_address.present?.should eq true
21
+ order.shipping_address.present?.should eq true
22
+
23
+ order.billing_address.valid?.should eq true
24
+ order.shipping_address.valid?.should eq true
25
+
26
+ order.billing_address.full_name.present?.should eq true
27
+ order.shipping_address.full_name.present?.should eq true
28
+
29
+ order.save.should eq true
30
+ end
31
+
32
+ end
@@ -0,0 +1,554 @@
1
+ require 'spec_helper'
2
+
3
+ describe Effective::QbMachine, "Basic Functionality" do
4
+
5
+ before :each do
6
+ end
7
+
8
+ it "should be valid" do
9
+ @qb_machine = Effective::QbMachine.new
10
+ @qb_machine.should be_valid
11
+ end
12
+
13
+ it "should initialize a ticket in the Ready state when constructed" do
14
+ @qb_machine = Effective::QbMachine.new
15
+ @qb_machine.ticket.state.should eql('Ready')
16
+ end
17
+
18
+ it "should not create a ticket if the ticket could not be found" do
19
+ @qb_machine = Effective::QbMachine.new(1976)
20
+ @qb_machine.ticket.should be_nil
21
+ end
22
+
23
+ it "should not be valid if a ticket could not be found" do
24
+ @qb_machine = Effective::QbMachine.new(1976)
25
+ @qb_machine.should_not be_valid
26
+ end
27
+
28
+ it "should be valid upon finding an existing ticket" do
29
+ @qb_ticket = Effective::QbTicket.create
30
+ @qb_machine = Effective::QbMachine.new(@qb_ticket.id)
31
+ @qb_machine.should be_valid
32
+ end
33
+
34
+ it "should delegate logging functionality to the ticket" do
35
+ before = Effective::QbLog.count
36
+ @qb_machine = Effective::QbMachine.new
37
+ @qb_machine.log('Message')
38
+ assert Effective::QbLog.count > before
39
+ end
40
+
41
+ it "should not record an empty log message" do
42
+ @qb_machine = Effective::QbMachine.new
43
+ Effective::QbLog.should_not_receive(:create)
44
+ @qb_machine.log('')
45
+ end
46
+
47
+ it "should record the last log message in an instance member" do
48
+ @qb_machine = Effective::QbMachine.new
49
+ message = 'A message'
50
+ @qb_machine.log(message)
51
+ @qb_machine.last_log_message.should eql(message)
52
+ end
53
+
54
+ end
55
+
56
+ describe Effective::QbMachine, "Authentication Behavior (op_authenticate)" do
57
+
58
+ before :each do
59
+ @qb_machine = Effective::QbMachine.new
60
+ end
61
+
62
+ it "should be valid" do
63
+ @qb_machine.should be_valid
64
+ end
65
+
66
+ it "should authenticate successfully with the correct password" do
67
+ result = @qb_machine.op_authenticate(EffectiveQbSync.quickbooks_username, EffectiveQbSync.quickbooks_password)
68
+ result.should_not eql('nvu')
69
+ end
70
+
71
+ it "should not authenticate successfully with a different password" do
72
+ result = @qb_machine.op_authenticate(EffectiveQbSync.quickbooks_username, '12345')
73
+ result.should eql('nvu')
74
+ end
75
+
76
+ it "should transition ticket to Finished state upon unsuccessful authentication" do
77
+ @qb_machine.should_receive(:authentication_valid?).and_return(false)
78
+ @qb_machine.op_authenticate(EffectiveQbSync.quickbooks_username,'incorrectpassword')
79
+ @qb_machine.ticket.state.should eql('Finished')
80
+ end
81
+
82
+ it "should populate last_error with authentication failure message upon unsuccessful authentication" do
83
+ @qb_machine.should_receive(:authentication_valid?).and_return(false)
84
+ @qb_machine.op_authenticate(EffectiveQbSync.quickbooks_username,'incorrectpassword')
85
+ @qb_machine.ticket.last_error.should_not be_blank
86
+ end
87
+
88
+ it "should keep ticket in the Authenticated state after authentication if there is work to be done" do
89
+ @qb_machine.should_receive(:authentication_valid?).and_return(true)
90
+ @qb_machine.should_receive(:has_work?).and_return(true)
91
+
92
+ @qb_machine.op_authenticate(EffectiveQbSync.quickbooks_username,EffectiveQbSync.quickbooks_password)
93
+ @qb_machine.ticket.state.should eql('Authenticated')
94
+ end
95
+
96
+ it "should transition ticket to Finished state if there is not any work to be done" do
97
+ @qb_machine.should_receive(:authentication_valid?).and_return(true)
98
+ @qb_machine.should_receive(:has_work?).and_return(false)
99
+
100
+ @qb_machine.op_authenticate(EffectiveQbSync.quickbooks_username,EffectiveQbSync.quickbooks_password)
101
+ @qb_machine.ticket.state.should eql('Finished')
102
+ end
103
+
104
+ it "should return 'nvu' from op_authentication if the user login is invalid" do
105
+ @qb_machine.should_receive(:authentication_valid?).and_return(false)
106
+ result = @qb_machine.op_authenticate(EffectiveQbSync.quickbooks_username,'incorrectpassword')
107
+ result.should eql('nvu')
108
+ end
109
+
110
+ it "should return 'none' from op_authentication if the login is valid but no work to be done" do
111
+ @qb_machine.should_receive(:authentication_valid?).and_return(true)
112
+ @qb_machine.should_receive(:has_work?).and_return(false)
113
+ result = @qb_machine.op_authenticate(EffectiveQbSync.quickbooks_username,EffectiveQbSync.quickbooks_password)
114
+ result.should eql('none')
115
+ end
116
+
117
+ it "should return '' from op_authentication if the login is valid and there is work to be done" do
118
+ @qb_machine.should_receive(:authentication_valid?).and_return(true)
119
+ @qb_machine.should_receive(:has_work?).and_return(true)
120
+ result = @qb_machine.op_authenticate(EffectiveQbSync.quickbooks_username,EffectiveQbSync.quickbooks_password)
121
+ result.should eql('')
122
+ end
123
+
124
+ it "should record the ticket username field on successful authentication" do
125
+ @qb_machine.should_receive(:authentication_valid?).and_return(true)
126
+ username = 'successful user'
127
+ @qb_machine.op_authenticate(username,'password')
128
+ @qb_machine.ticket.username.should eql(username)
129
+ end
130
+
131
+ it "should record the ticket username field on unsuccessful authentication" do
132
+ @qb_machine.should_receive(:authentication_valid?).and_return(false)
133
+ username = 'unsuccessful user'
134
+ @qb_machine.op_authenticate(username,'password')
135
+ @qb_machine.ticket.username.should eql(username)
136
+ end
137
+
138
+ it "should not have a current request after successful authentication" do
139
+ @qb_machine.should_receive(:authentication_valid?).and_return(true)
140
+ @qb_machine.op_authenticate(EffectiveQbSync.quickbooks_username,EffectiveQbSync.quickbooks_password)
141
+ @qb_machine.ticket.qb_request.should be_nil
142
+ end
143
+
144
+ end
145
+
146
+ describe Effective::QbMachine, "Sending Request qbXML to QuickBooks (op_send_request_xml)" do
147
+
148
+ before :each do
149
+ @qb_machine = Effective::QbMachine.new
150
+ allow(@qb_machine).to receive(:authentication_valid?).and_return(true)
151
+ allow(@qb_machine).to receive(:has_work?).and_return(true)
152
+ @qb_machine.op_authenticate(EffectiveQbSync.quickbooks_username,EffectiveQbSync.quickbooks_password)
153
+
154
+ # Here the machine should be in the Authenticated State
155
+
156
+ # - signature: string sendRequestXML(ticket, hcpresponse, company, country, major_ver, minor_ver)
157
+
158
+ @hcpresponse = 'some response'
159
+ @company = 'some company'
160
+ @country = 'US'
161
+ @major_ver = '8'
162
+ @minor_ver = '2'
163
+
164
+ @default_request_params = {
165
+ :hcpresponse=>@hcpresponse,
166
+ :company=>@company,
167
+ :country=>@country,
168
+ :major_ver=>@major_ver,
169
+ :minor_ver=>@minor_ver
170
+ }
171
+
172
+ # the default order item we'll be using when mocking out QbMachine#create_request
173
+ @order = Effective::Order.new
174
+
175
+ # the default request we'll use when mocking out QbMachine#create_request
176
+ @qb_request = Effective::QbRequest.new(request_type: 'OrderItemSynchronization', order: @order)
177
+ allow(@qb_request).to receive(:to_qb_xml).and_return('<qbXML></qbXML>') # we are not worried about qbXML correctness here
178
+ allow(@qb_machine).to receive(:create_request).and_return(@qb_request)
179
+ end
180
+
181
+ it "should populate ticket fields [hpc_response,company_file_name, etc] when non-blank" do
182
+ allow(@qb_machine).to receive(:has_work?).and_return(false)
183
+ @qb_machine.op_send_request_xml(@default_request_params)
184
+
185
+ @qb_machine.ticket.hpc_response.should eql(@hcpresponse)
186
+ @qb_machine.ticket.company_file_name.should eql(@company)
187
+ @qb_machine.ticket.country.should eql(@country)
188
+ @qb_machine.ticket.qbxml_major_version.should eql(@major_ver)
189
+ @qb_machine.ticket.qbxml_minor_version.should eql(@minor_ver)
190
+ end
191
+
192
+ it "should not overwrite ticket fields [hpc_response,company_file_name, etc] on subsequent request XML calls if those fields are blank" do
193
+ allow(@qb_machine).to receive(:has_work?).and_return(false)
194
+ @qb_machine.op_send_request_xml(@default_request_params)
195
+
196
+ @qb_machine.ticket.hpc_response.should eql(@hcpresponse)
197
+ @qb_machine.ticket.company_file_name.should eql(@company)
198
+ @qb_machine.ticket.country.should eql(@country)
199
+ @qb_machine.ticket.qbxml_major_version.should eql(@major_ver)
200
+
201
+ # now blank out the fields and call again
202
+
203
+ @qb_machine.op_send_request_xml(@default_request_params.except(:hcpresponse,:company,:country,:major_ver,:minor_ver))
204
+
205
+ @qb_machine.ticket.hpc_response.should eql(@hcpresponse)
206
+ @qb_machine.ticket.company_file_name.should eql(@company)
207
+ @qb_machine.ticket.country.should eql(@country)
208
+ @qb_machine.ticket.qbxml_major_version.should eql(@major_ver)
209
+ end
210
+
211
+ it "should transition ticket to the RequestError state if the ticket is not in the Authenticated or Processing states" do
212
+ @qb_machine.ticket.update_attributes!(state: 'Finished')
213
+ @qb_machine.op_send_request_xml(@default_request_params)
214
+ @qb_machine.ticket.state.should eql('RequestError')
215
+ end
216
+
217
+ it "should return a non-empty string if there is work to be done" do
218
+ allow(@qb_machine).to receive(:has_work?).and_return(true)
219
+
220
+ result = @qb_machine.op_send_request_xml(@default_request_params)
221
+ result.should_not be_blank
222
+ end
223
+
224
+ it "should create a QbRequest model and attach to the ticket for the corresponding qbXML" do
225
+ allow(@qb_machine).to receive(:has_work?).and_return(true)
226
+ @qb_machine.op_send_request_xml(@default_request_params)
227
+ @qb_request.id.should_not be_nil
228
+ @qb_machine.ticket.qb_requests.should_not be_empty
229
+ end
230
+
231
+ it "should only create one QbRequest model when sending request xml" do
232
+ allow(@qb_machine).to receive(:has_work?).and_return(true)
233
+ @qb_machine.op_send_request_xml(@default_request_params)
234
+ @qb_machine.ticket.qb_requests.size.should eql(1)
235
+ end
236
+
237
+ it "should put the QbRequest model into the Processing state after sending work to QuickBooks" do
238
+ allow(@qb_machine).to receive(:has_work?).and_return(true)
239
+ @qb_machine.op_send_request_xml(@default_request_params)
240
+ request = @qb_machine.ticket.qb_requests.first
241
+ request.state.should eql('Processing')
242
+ end
243
+
244
+ it "should set the QbTicket model into the Processing state after sending work to QuickBooks" do
245
+ allow(@qb_machine).to receive(:has_work?).and_return(true)
246
+ @qb_machine.op_send_request_xml(@default_request_params)
247
+ @qb_machine.ticket.state.should eql('Processing')
248
+ end
249
+
250
+ it "should store the qbXML into the QbRequest model" do
251
+ allow(@qb_machine).to receive(:has_work?).and_return(true)
252
+ qbXML = @qb_machine.op_send_request_xml(@default_request_params)
253
+ request = @qb_machine.ticket.qb_requests.first
254
+ request.request_qbxml.should eql(qbXML)
255
+ end
256
+
257
+ it "should set the request_sent_at field in the QbRequest model" do
258
+ allow(@qb_machine).to receive(:has_work?).and_return(true)
259
+ qbXML = @qb_machine.op_send_request_xml(@default_request_params)
260
+ request = @qb_machine.ticket.qb_requests.first
261
+ request.request_sent_at.should_not be_nil
262
+ end
263
+
264
+ it "should set the current request in the ticket" do
265
+ allow(@qb_machine).to receive(:has_work?).and_return(true)
266
+ @qb_machine.op_send_request_xml(@default_request_params)
267
+ @qb_machine.ticket.qb_request.should_not be_nil
268
+ @qb_machine.ticket.qb_request.should eql(@qb_request)
269
+ end
270
+
271
+ end
272
+
273
+
274
+ describe Effective::QbMachine, "Receiving response qbXML from QuickBooks (op_receive_response_xml)" do
275
+
276
+ before :each do
277
+ @qb_machine = Effective::QbMachine.new
278
+
279
+ # fake out authentication to pass successfully
280
+ allow(@qb_machine).to receive(:authentication_valid?).and_return(true)
281
+ allow(@qb_machine).to receive(:has_work?).and_return(true)
282
+
283
+ @qb_machine.op_authenticate(EffectiveQbSync.quickbooks_username, EffectiveQbSync.quickbooks_password)
284
+
285
+ @default_request_params = {
286
+ :hcpresponse=>'some response',
287
+ :company=>'some company',
288
+ :country=>'US',
289
+ :major_ver=>'8',
290
+ :minor_ver=>'2'
291
+ }
292
+
293
+ # the default order item we'll be using when mocking out QbMachine#create_request
294
+ @order = Effective::Order.new
295
+ @qb_request = Effective::QbRequest.new(id: 1, request_type: 'OrderItemSynchronization', order: @order, state: 'CustomerQuery')
296
+ @qb_request_xml = '<qbXML></qbXML>'
297
+ allow(@qb_request).to receive(:to_qb_xml).and_return(@qb_request_xml) # we are not worried about qbXML correctness here
298
+
299
+ # set the machine to send some request qbXML to quickbooks
300
+ allow(@qb_machine).to receive(:has_work?).and_return(true)
301
+ allow(@qb_machine).to receive(:create_request).and_return(@qb_request)
302
+ @qb_machine.op_send_request_xml(@default_request_params)
303
+
304
+ @qb_response_xml = '<qbXML><QBXMLMsgsRs><AccountQueryRs requestID="1" statusCode="0"></AccountQueryRs></QBXMLMsgsRs></qbXML>'
305
+ # int receiveResponseXML(ticket, response, hresult, message)
306
+ @default_response_params = {
307
+ :response=>@qb_response_xml,
308
+ :hresult=>'',
309
+ :message=>''
310
+ }
311
+
312
+ # by default always report no more work to be done
313
+ allow(@qb_machine).to receive(:how_much_more_work).and_return(0)
314
+ end
315
+
316
+ it "should return -1 to indicate error if the ticket state is not in the Processing state" do
317
+ @qb_machine.ticket.update_attributes! :state=>'Finished'
318
+ result = @qb_machine.op_receive_response_xml(@default_response_params)
319
+ result.should eql(-1)
320
+ end
321
+
322
+ it "should set the ticket state to RequestError if the ticket state was previously Authenticated" do
323
+ @qb_machine.ticket.update_attributes! :state=>'Authenticated'
324
+ @qb_machine.op_receive_response_xml(@default_response_params)
325
+ @qb_machine.ticket.state.should eql('RequestError')
326
+ end
327
+
328
+ it "should set the ticket state to RequestError if the ticket state was previously Ready" do
329
+ @qb_machine.ticket.update_attributes! :state=>'Ready'
330
+ @qb_machine.op_receive_response_xml(@default_response_params)
331
+ @qb_machine.ticket.state.should eql('RequestError')
332
+ end
333
+
334
+ it "should set the ticket state to RequestError if the ticket state was previously Finished" do
335
+ @qb_machine.ticket.update_attributes! :state=>'Finished'
336
+ @qb_machine.op_receive_response_xml(@default_response_params)
337
+ @qb_machine.ticket.state.should eql('RequestError')
338
+ end
339
+
340
+ it "should send the ticket into a RequestError state if there is no matching Processing request" do
341
+ allow(@qb_machine).to receive(:find_outstanding_request).and_return(nil)
342
+ @qb_machine.op_receive_response_xml(@default_response_params)
343
+ @qb_machine.ticket.state.should eql('RequestError')
344
+ end
345
+
346
+ it "should set the request state to Error if the response indicates error" do
347
+ allow(@qb_machine).to receive(:find_outstanding_request).and_return(@qb_request)
348
+ allow(@qb_request).to receive(:consume_response_xml).and_return(false)
349
+ @qb_machine.op_receive_response_xml(@default_response_params)
350
+ @qb_request.state.should eql('Error')
351
+ end
352
+
353
+ it "should set the ticket last_error field if the response indicates error" do
354
+ allow(@qb_machine).to receive(:find_outstanding_request).and_return(@qb_request)
355
+ allow(@qb_request).to receive(:consume_response_xml).and_return(false)
356
+ @qb_machine.op_receive_response_xml(@default_response_params)
357
+ @qb_machine.ticket.last_error.should_not be_blank
358
+ end
359
+
360
+ it "should set the request state to Error if there was a connection error with QuickBooks" do
361
+ allow(@qb_machine).to receive(:find_outstanding_request).and_return(@qb_request)
362
+ allow(@qb_request).to receive(:consume_response_xml).and_return(true)
363
+
364
+ @params = @default_response_params
365
+ @params[:hresult] = 'error_result'
366
+ @params[:message] = 'error_message'
367
+
368
+ @qb_machine.op_receive_response_xml(@params)
369
+ @qb_request.state.should eql('Error')
370
+ end
371
+
372
+ it "should set the set the ticket state to RequestError if there was a connection error with QuickBooks" do
373
+ allow(@qb_machine).to receive(:find_outstanding_request).and_return(@qb_request)
374
+ allow(@qb_request).to receive(:consume_response_xml).and_return(true)
375
+
376
+ @params = @default_response_params
377
+ @params[:hresult] = 'error_result'
378
+ @params[:message] = 'error_message'
379
+
380
+ @qb_machine.op_receive_response_xml(@params)
381
+ @qb_machine.ticket.state.should eql('RequestError')
382
+ end
383
+
384
+ it "should set the ticket last_error field if there was a connection error with QuickBooks" do
385
+ allow(@qb_machine).to receive(:find_outstanding_request).and_return(@qb_request)
386
+ allow(@qb_request).to receive(:consume_response_xml).and_return(true)
387
+
388
+ @params = @default_response_params
389
+ @params[:hresult] = 'error_result'
390
+ @params[:message] = 'error_message'
391
+
392
+ @qb_machine.op_receive_response_xml(@params)
393
+ @qb_machine.ticket.last_error.should_not be_blank
394
+ end
395
+
396
+ it "should return -1 if there was a connection error with QuickBooks" do
397
+ allow(@qb_machine).to receive(:find_outstanding_request).and_return(@qb_request)
398
+ allow(@qb_request).to receive(:consume_response_xml).and_return(true)
399
+
400
+ @params = @default_response_params
401
+ @params[:hresult] = 'error_result'
402
+ @params[:message] = 'error_message'
403
+
404
+ response = @qb_machine.op_receive_response_xml(@params)
405
+ response.should eql(-1)
406
+ end
407
+
408
+ it "should set the ticket connection_error_hresult and connection_error_message fields upon a connection error with QuickBooks" do
409
+ allow(@qb_machine).to receive(:find_outstanding_request).and_return(@qb_request)
410
+ allow(@qb_request).to receive(:consume_response_xml).and_return(true)
411
+
412
+ @params = @default_response_params
413
+ @params[:hresult] = 'error_result'
414
+ @params[:message] = 'error_message'
415
+
416
+ @qb_machine.op_receive_response_xml(@params)
417
+ @qb_machine.ticket.connection_error_hresult.should_not be_blank
418
+ @qb_machine.ticket.connection_error_message.should_not be_blank
419
+ end
420
+
421
+ it "should return a number between 1 and 99, inclusive, if there is more work to be done" do
422
+ allow(@qb_machine).to receive(:find_outstanding_request).and_return(@qb_request)
423
+ allow(@qb_request).to receive(:consume_response_xml).and_return(true)
424
+ allow(@qb_machine).to receive(:how_much_more_work).and_return(15)
425
+
426
+ result = @qb_machine.op_receive_response_xml(@default_response_params)
427
+ result.should be > 0
428
+ result.should be < 100
429
+ end
430
+
431
+ it "should set the response_qbxml field in the QbRequest model" do
432
+ allow(@qb_machine).to receive(:find_outstanding_request).and_return(@qb_request)
433
+ allow(@qb_request).to receive(:consume_response_xml).and_return(true)
434
+
435
+ @qb_request.request_type = nil
436
+
437
+ @qb_machine.op_receive_response_xml(@default_response_params)
438
+
439
+ @qb_request.response_qbxml.should eql(@qb_response_xml)
440
+ end
441
+
442
+ it "should release the ticket's current request if that request signals that it is done" do
443
+ allow(@qb_machine).to receive(:find_outstanding_request).and_return(@qb_request)
444
+ @qb_request.state = 'Finished'
445
+ allow(@qb_request).to receive(:consume_response_xml).and_return(true)
446
+
447
+ # @qb_request.stub!(:consume_response_xml).and_return {
448
+ # @qb_request.state = 'Finished'
449
+ # # return true to indicate success
450
+ # true
451
+ # }
452
+ @qb_machine.op_receive_response_xml(@default_response_params)
453
+
454
+ # it should be nil because the request is in a finished state
455
+ @qb_machine.ticket.qb_request.should be_nil
456
+ end
457
+
458
+ end
459
+
460
+
461
+ describe Effective::QbMachine, "Receiving notification that the QBWC cannot connect to QuickBooks (op_connection_error)" do
462
+
463
+ before :each do
464
+ @qb_machine = Effective::QbMachine.new
465
+ @hresult = 'hresult'
466
+ @message = 'message'
467
+ @result = @qb_machine.op_connection_error(@hresult, @message)
468
+ end
469
+
470
+ it "should always return 'done'" do
471
+ @result.should eql('done')
472
+ end
473
+
474
+ it "should store the hresult and message in the ticket" do
475
+ @qb_machine.ticket.connection_error_hresult.should eql(@hresult)
476
+ @qb_machine.ticket.connection_error_message.should eql(@message)
477
+ end
478
+
479
+ it "should set the ticket state to ConnectionError" do
480
+ @qb_machine.ticket.state.should eql('ConnectionError')
481
+ end
482
+
483
+ end
484
+
485
+ describe Effective::QbMachine, "Receiving a request from the QBWC to provide the last error message (op_last_error)" do
486
+
487
+ before :each do
488
+ @qb_machine = Effective::QbMachine.new
489
+ @last_error = 'What?'
490
+ @qb_machine.ticket.update_attributes! :last_error=>@last_error
491
+ end
492
+
493
+ it "should return the last error" do
494
+ error = @qb_machine.op_last_error
495
+ end
496
+
497
+ it "should return '' if the last error is blank" do
498
+ @qb_machine.ticket.update_attributes! :last_error=>nil
499
+ error = @qb_machine.op_last_error
500
+ error.should eql('')
501
+
502
+ @qb_machine.ticket.update_attributes! :last_error=>''
503
+ error = @qb_machine.op_last_error
504
+ error.should eql('')
505
+ end
506
+
507
+ end
508
+
509
+ describe Effective::QbMachine, "Closing the connection (op_close_connection)" do
510
+
511
+ before :each do
512
+ @qb_machine = Effective::QbMachine.new
513
+ end
514
+
515
+ it "should not transition ticket state on close_connection if state is Finished" do
516
+ @qb_machine.ticket.update_attributes! :state=>'Finished'
517
+ @qb_machine.op_close_connection
518
+ @qb_machine.ticket.state.should eql('Finished')
519
+ end
520
+
521
+ it "should not transition ticket state on close_connection if state is ConnectionError" do
522
+ @qb_machine.ticket.update_attributes! :state=>'ConnectionError'
523
+ @qb_machine.op_close_connection
524
+ @qb_machine.ticket.state.should eql('ConnectionError')
525
+ end
526
+
527
+ it "should not transition ticket state on close_connection if state is RequestError" do
528
+ @qb_machine.ticket.update_attributes! :state=>'RequestError'
529
+ @qb_machine.op_close_connection
530
+ @qb_machine.ticket.state.should eql('RequestError')
531
+ end
532
+
533
+ it "should transition ticket state to Finished if state is Ready " do
534
+ @qb_machine.ticket.update_attributes! :state=>'Ready'
535
+ @qb_machine.op_close_connection
536
+ @qb_machine.ticket.state.should eql('Finished')
537
+ end
538
+
539
+ it "should transition ticket state to Finished if state is Authenticated" do
540
+ @qb_machine.ticket.update_attributes! :state=>'Authenticated'
541
+ @qb_machine.op_close_connection
542
+ @qb_machine.ticket.state.should eql('Finished')
543
+ end
544
+
545
+ it "should transition ticket state to Finished if state is Processing" do
546
+ @qb_machine.ticket.update_attributes! :state=>'Processing'
547
+ @qb_machine.op_close_connection
548
+ @qb_machine.ticket.state.should eql('Finished')
549
+ end
550
+
551
+
552
+ end
553
+
554
+