netsuite 0.8.2 → 0.8.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +5 -5
- data/.gitignore +1 -0
- data/.ruby-version +1 -1
- data/.tool-versions +1 -0
- data/Gemfile +4 -3
- data/README.md +129 -38
- data/circle.yml +36 -13
- data/lib/netsuite.rb +40 -19
- data/lib/netsuite/actions/login.rb +20 -1
- data/lib/netsuite/actions/search.rb +1 -6
- data/lib/netsuite/actions/update.rb +6 -2
- data/lib/netsuite/actions/update_list.rb +109 -0
- data/lib/netsuite/actions/upsert.rb +2 -0
- data/lib/netsuite/configuration.rb +34 -4
- data/lib/netsuite/errors.rb +1 -0
- data/lib/netsuite/records/accounting_period.rb +2 -2
- data/lib/netsuite/records/assembly_build.rb +4 -1
- data/lib/netsuite/records/assembly_item.rb +1 -0
- data/lib/netsuite/records/assembly_unbuild.rb +3 -0
- data/lib/netsuite/records/bin_number.rb +18 -0
- data/lib/netsuite/records/bin_number_list.rb +1 -20
- data/lib/netsuite/records/bin_transfer.rb +38 -0
- data/lib/netsuite/records/bin_transfer_inventory.rb +20 -0
- data/lib/netsuite/records/bin_transfer_inventory_list.rb +10 -0
- data/lib/netsuite/records/cash_refund_item.rb +1 -1
- data/lib/netsuite/records/classification.rb +5 -2
- data/lib/netsuite/records/contact.rb +1 -1
- data/lib/netsuite/records/credit_memo.rb +1 -1
- data/lib/netsuite/records/custom_field_list.rb +10 -2
- data/lib/netsuite/records/custom_record.rb +3 -3
- data/lib/netsuite/records/custom_record_ref.rb +1 -0
- data/lib/netsuite/records/customer.rb +5 -4
- data/lib/netsuite/records/customer_credit_cards.rb +36 -0
- data/lib/netsuite/records/customer_credit_cards_list.rb +10 -0
- data/lib/netsuite/records/customer_deposit.rb +9 -6
- data/lib/netsuite/records/customer_payment.rb +6 -2
- data/lib/netsuite/records/customer_payment_credit.rb +17 -0
- data/lib/netsuite/records/customer_payment_credit_list.rb +12 -0
- data/lib/netsuite/records/customer_sales_team.rb +24 -0
- data/lib/netsuite/records/customer_sales_team_list.rb +9 -0
- data/lib/netsuite/records/customer_status.rb +29 -0
- data/lib/netsuite/records/customer_subscription.rb +18 -0
- data/lib/netsuite/records/customer_subscriptions_list.rb +10 -0
- data/lib/netsuite/records/employee.rb +1 -1
- data/lib/netsuite/records/entity_custom_field.rb +53 -0
- data/lib/netsuite/records/estimate.rb +42 -0
- data/lib/netsuite/records/estimate_item.rb +40 -0
- data/lib/netsuite/records/estimate_item_list.rb +11 -0
- data/lib/netsuite/records/inbound_shipment.rb +33 -0
- data/lib/netsuite/records/inbound_shipment_item.rb +39 -0
- data/lib/netsuite/records/inbound_shipment_item_list.rb +11 -0
- data/lib/netsuite/records/inter_company_journal_entry.rb +48 -0
- data/lib/netsuite/records/inter_company_journal_entry_line.rb +28 -0
- data/lib/netsuite/records/inter_company_journal_entry_line_list.rb +14 -0
- data/lib/netsuite/records/inventory_item.rb +3 -2
- data/lib/netsuite/records/invoice.rb +1 -1
- data/lib/netsuite/records/item_fulfillment.rb +1 -1
- data/lib/netsuite/records/lot_numbered_inventory_item.rb +116 -0
- data/lib/netsuite/records/matrix_option_list.rb +12 -4
- data/lib/netsuite/records/message.rb +30 -0
- data/lib/netsuite/records/non_inventory_resale_item.rb +3 -2
- data/lib/netsuite/records/non_inventory_sale_item.rb +1 -1
- data/lib/netsuite/records/other_charge_sale_item.rb +2 -2
- data/lib/netsuite/records/partner.rb +7 -5
- data/lib/netsuite/records/price.rb +17 -0
- data/lib/netsuite/records/price_level.rb +26 -0
- data/lib/netsuite/records/price_list.rb +9 -0
- data/lib/netsuite/records/pricing.rb +20 -0
- data/lib/netsuite/records/pricing_matrix.rb +2 -2
- data/lib/netsuite/records/promotions.rb +26 -0
- data/lib/netsuite/records/promotions_list.rb +9 -0
- data/lib/netsuite/records/return_authorization_item.rb +1 -1
- data/lib/netsuite/records/sales_order.rb +1 -0
- data/lib/netsuite/records/sales_order_item.rb +12 -5
- data/lib/netsuite/records/sales_role.rb +26 -0
- data/lib/netsuite/records/sales_tax_item.rb +3 -1
- data/lib/netsuite/records/serialized_assembly_item.rb +239 -0
- data/lib/netsuite/records/service_resale_item.rb +1 -1
- data/lib/netsuite/records/service_sale_item.rb +1 -1
- data/lib/netsuite/records/support_case.rb +1 -1
- data/lib/netsuite/records/support_case_type.rb +26 -0
- data/lib/netsuite/records/tax_group.rb +2 -2
- data/lib/netsuite/records/transaction_body_custom_field.rb +61 -0
- data/lib/netsuite/records/transaction_column_custom_field.rb +59 -0
- data/lib/netsuite/records/vendor.rb +2 -1
- data/lib/netsuite/records/vendor_credit.rb +2 -0
- data/lib/netsuite/records/vendor_currency.rb +26 -0
- data/lib/netsuite/records/vendor_currency_list.rb +9 -0
- data/lib/netsuite/records/work_order.rb +8 -0
- data/lib/netsuite/support/actions.rb +2 -0
- data/lib/netsuite/support/country.rb +27 -15
- data/lib/netsuite/support/search_result.rb +20 -5
- data/lib/netsuite/utilities.rb +83 -21
- data/lib/netsuite/version.rb +1 -1
- data/netsuite.gemspec +4 -3
- data/spec/netsuite/actions/login_spec.rb +23 -0
- data/spec/netsuite/actions/update_list_spec.rb +107 -0
- data/spec/netsuite/actions/update_spec.rb +42 -0
- data/spec/netsuite/configuration_spec.rb +111 -6
- data/spec/netsuite/records/address_spec.rb +10 -0
- data/spec/netsuite/records/basic_record_spec.rb +19 -2
- data/spec/netsuite/records/bin_number_spec.rb +23 -0
- data/spec/netsuite/records/classification_spec.rb +10 -1
- data/spec/netsuite/records/custom_field_list_spec.rb +39 -4
- data/spec/netsuite/records/custom_record_spec.rb +1 -1
- data/spec/netsuite/records/customer_credit_cards_list_spec.rb +23 -0
- data/spec/netsuite/records/customer_payment_credit_list_spec.rb +26 -0
- data/spec/netsuite/records/customer_payment_spec.rb +1 -6
- data/spec/netsuite/records/customer_sales_team_list_spec.rb +41 -0
- data/spec/netsuite/records/customer_spec.rb +44 -2
- data/spec/netsuite/records/customer_subscription_spec.rb +41 -0
- data/spec/netsuite/records/customer_subscriptions_list_spec.rb +19 -0
- data/spec/netsuite/records/employee_spec.rb +2 -2
- data/spec/netsuite/records/entity_custom_field_spec.rb +34 -0
- data/spec/netsuite/records/estimate_item_list_spec.rb +26 -0
- data/spec/netsuite/records/estimate_item_spec.rb +40 -0
- data/spec/netsuite/records/estimate_spec.rb +216 -0
- data/spec/netsuite/records/inter_company_journal_entry_line_list_spec.rb +26 -0
- data/spec/netsuite/records/inter_company_journal_entry_line_spec.rb +60 -0
- data/spec/netsuite/records/inter_company_journal_entry_spec.rb +156 -0
- data/spec/netsuite/records/inventory_item_spec.rb +57 -0
- data/spec/netsuite/records/matrix_option_list_spec.rb +15 -5
- data/spec/netsuite/records/message_spec.rb +49 -0
- data/spec/netsuite/records/non_inventory_resale_item_spec.rb +165 -0
- data/spec/netsuite/records/non_inventory_sale_item_spec.rb +1 -1
- data/spec/netsuite/records/partner_spec.rb +143 -0
- data/spec/netsuite/records/price_level_spec.rb +16 -0
- data/spec/netsuite/records/pricing_matrix_spec.rb +15 -13
- data/spec/netsuite/records/return_authorization_item_spec.rb +1 -1
- data/spec/netsuite/records/sales_order_item_spec.rb +11 -5
- data/spec/netsuite/records/service_resale_item_spec.rb +134 -0
- data/spec/netsuite/records/support_case_type_spec.rb +22 -0
- data/spec/netsuite/records/transaction_body_custom_field_spec.rb +32 -0
- data/spec/netsuite/records/transaction_column_custom_field_spec.rb +32 -0
- data/spec/netsuite/records/vendor_credit_spec.rb +29 -0
- data/spec/netsuite/records/vendor_spec.rb +1 -1
- data/spec/netsuite/support/search_result_spec.rb +24 -0
- data/spec/netsuite/utilities_spec.rb +44 -6
- data/spec/spec_helper.rb +5 -4
- data/spec/support/fixtures/update_list/update_list_items.xml +22 -0
- data/spec/support/fixtures/update_list/update_list_one_item.xml +18 -0
- data/spec/support/fixtures/update_list/update_list_with_errors.xml +32 -0
- metadata +111 -11
|
@@ -17,9 +17,10 @@ module NetSuite
|
|
|
17
17
|
# <platformCore:pageIndex>1</platformCore:pageIndex>
|
|
18
18
|
# <platformCore:searchId>WEBSERVICES_738944_SB2_03012013650784545962753432_28d96bd280</platformCore:searchId>
|
|
19
19
|
|
|
20
|
-
def initialize(response, result_class)
|
|
20
|
+
def initialize(response, result_class, credentials)
|
|
21
21
|
@result_class = result_class
|
|
22
22
|
@response = response
|
|
23
|
+
@credentials = credentials
|
|
23
24
|
|
|
24
25
|
@total_records = response.body[:total_records].to_i
|
|
25
26
|
@total_pages = response.body[:total_pages].to_i
|
|
@@ -28,8 +29,19 @@ module NetSuite
|
|
|
28
29
|
if @total_records > 0
|
|
29
30
|
if response.body.has_key?(:record_list)
|
|
30
31
|
# basic search results
|
|
31
|
-
|
|
32
|
-
|
|
32
|
+
|
|
33
|
+
# `recordList` node can contain several nested `record` nodes, only one node or be empty
|
|
34
|
+
# so we have to handle all these cases:
|
|
35
|
+
# * { record_list: nil }
|
|
36
|
+
# * { record_list: { record: => {...} } }
|
|
37
|
+
# * { record_list: { record: => [{...}, {...}, ...] } }
|
|
38
|
+
record_list = if response.body[:record_list].nil?
|
|
39
|
+
[]
|
|
40
|
+
elsif response.body[:record_list][:record].is_a?(Array)
|
|
41
|
+
response.body[:record_list][:record]
|
|
42
|
+
else
|
|
43
|
+
[response.body[:record_list][:record]]
|
|
44
|
+
end
|
|
33
45
|
|
|
34
46
|
record_list.each do |record|
|
|
35
47
|
results << result_class.new(record)
|
|
@@ -98,8 +110,11 @@ module NetSuite
|
|
|
98
110
|
yield results
|
|
99
111
|
|
|
100
112
|
next_search = @result_class.search(
|
|
101
|
-
|
|
102
|
-
|
|
113
|
+
{
|
|
114
|
+
search_id: @response.body[:search_id],
|
|
115
|
+
page_index: @response.body[:page_index].to_i + 1
|
|
116
|
+
},
|
|
117
|
+
@credentials
|
|
103
118
|
)
|
|
104
119
|
|
|
105
120
|
@results = next_search.results
|
data/lib/netsuite/utilities.rb
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
require 'date'
|
|
2
|
+
|
|
1
3
|
module NetSuite
|
|
2
4
|
module Utilities
|
|
3
5
|
extend self
|
|
@@ -37,6 +39,36 @@ module NetSuite
|
|
|
37
39
|
server_time_response.body[:get_server_time_response][:get_server_time_result][:server_time]
|
|
38
40
|
end
|
|
39
41
|
|
|
42
|
+
def netsuite_data_center_urls(account_id)
|
|
43
|
+
data_center_call_response = NetSuite::Configuration.connection({
|
|
44
|
+
# NOTE force a production WSDL so the sandbox settings are ignored
|
|
45
|
+
# as of 1/20/18 NS will start using the account ID to determine
|
|
46
|
+
# if a account is sandbox (123_SB1) as opposed to using a sandbox domain
|
|
47
|
+
|
|
48
|
+
wsdl: 'https://webservices.netsuite.com/wsdl/v2017_2_0/netsuite.wsdl',
|
|
49
|
+
|
|
50
|
+
# NOTE don't inherit default namespace settings, it includes the API version
|
|
51
|
+
namespaces: {
|
|
52
|
+
'xmlns:platformCore' => "urn:core_2017_2.platform.webservices.netsuite.com"
|
|
53
|
+
},
|
|
54
|
+
|
|
55
|
+
soap_header: {}
|
|
56
|
+
}).call(:get_data_center_urls, message: {
|
|
57
|
+
'platformMsgs:account' => account_id
|
|
58
|
+
})
|
|
59
|
+
|
|
60
|
+
if data_center_call_response.success?
|
|
61
|
+
data_center_call_response.body[:get_data_center_urls_response][:get_data_center_urls_result][:data_center_urls]
|
|
62
|
+
else
|
|
63
|
+
false
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
# TODO consider what to dop with this duplicate data center implementation
|
|
68
|
+
def data_center_url(*args)
|
|
69
|
+
DataCenter.get(*args)
|
|
70
|
+
end
|
|
71
|
+
|
|
40
72
|
def backoff(options = {})
|
|
41
73
|
# TODO the default backoff attempts should be customizable the global config
|
|
42
74
|
options[:attempts] ||= 8
|
|
@@ -46,7 +78,7 @@ module NetSuite
|
|
|
46
78
|
begin
|
|
47
79
|
count += 1
|
|
48
80
|
yield
|
|
49
|
-
rescue
|
|
81
|
+
rescue StandardError => e
|
|
50
82
|
exceptions_to_retry = [
|
|
51
83
|
Errno::ECONNRESET,
|
|
52
84
|
Errno::ETIMEDOUT,
|
|
@@ -83,12 +115,20 @@ module NetSuite
|
|
|
83
115
|
# https://github.com/stripe/stripe-netsuite/issues/815
|
|
84
116
|
if !e.message.include?("Only one request may be made against a session at a time") &&
|
|
85
117
|
!e.message.include?('java.util.ConcurrentModificationException') &&
|
|
118
|
+
!e.message.include?('java.lang.NullPointerException') &&
|
|
119
|
+
!e.message.include?('java.lang.IllegalStateException') &&
|
|
120
|
+
!e.message.include?('java.lang.reflect.InvocationTargetException') &&
|
|
86
121
|
!e.message.include?('com.netledger.common.exceptions.NLDatabaseOfflineException') &&
|
|
87
122
|
!e.message.include?('com.netledger.database.NLConnectionUtil$NoCompanyDbsOnlineException') &&
|
|
88
123
|
!e.message.include?('com.netledger.cache.CacheUnavailableException') &&
|
|
124
|
+
!e.message.include?('java.lang.IllegalStateException') &&
|
|
89
125
|
!e.message.include?('An unexpected error occurred.') &&
|
|
126
|
+
!e.message.include?('An unexpected error has occurred. Technical Support has been alerted to this problem.') &&
|
|
90
127
|
!e.message.include?('Session invalidation is in progress with different thread') &&
|
|
128
|
+
!e.message.include?('[missing resource APP:ERRORMESSAGE:WS_AN_UNEXPECTED_ERROR_OCCURRED] [missing resource APP:ERRORMESSAGE:ERROR_ID_1]') &&
|
|
91
129
|
!e.message.include?('SuiteTalk concurrent request limit exceeded. Request blocked.') &&
|
|
130
|
+
# maintenance is the new outage: this message is being used for intermittent errors
|
|
131
|
+
!e.message.include?('The account you are trying to access is currently unavailable while we undergo our regularly scheduled maintenance.') &&
|
|
92
132
|
!e.message.include?('The Connection Pool is not intialized.') &&
|
|
93
133
|
# it looks like NetSuite mispelled their error message...
|
|
94
134
|
!e.message.include?('The Connection Pool is not intiialized.')
|
|
@@ -109,15 +149,16 @@ module NetSuite
|
|
|
109
149
|
|
|
110
150
|
def request_failed?(ns_object)
|
|
111
151
|
return false if ns_object.errors.nil? || ns_object.errors.empty?
|
|
152
|
+
ns_object.errors.any? { |x| x.type == "ERROR" }
|
|
153
|
+
end
|
|
112
154
|
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
# end
|
|
155
|
+
def get_field_options(recordType, fieldName)
|
|
156
|
+
options = NetSuite::Records::BaseRefList.get_select_value(
|
|
157
|
+
field: fieldName,
|
|
158
|
+
recordType: recordType
|
|
159
|
+
)
|
|
119
160
|
|
|
120
|
-
|
|
161
|
+
options.base_refs
|
|
121
162
|
end
|
|
122
163
|
|
|
123
164
|
def get_item(ns_item_internal_id, opts = {})
|
|
@@ -133,6 +174,7 @@ module NetSuite
|
|
|
133
174
|
ns_item ||= NetSuite::Utilities.get_record(NetSuite::Records::KitItem, ns_item_internal_id, opts)
|
|
134
175
|
ns_item ||= NetSuite::Utilities.get_record(NetSuite::Records::SerializedInventoryItem, ns_item_internal_id, opts)
|
|
135
176
|
ns_item ||= NetSuite::Utilities.get_record(NetSuite::Records::LotNumberedAssemblyItem, ns_item_internal_id, opts)
|
|
177
|
+
ns_item ||= NetSuite::Utilities.get_record(NetSuite::Records::LotNumberedInventoryItem, ns_item_internal_id, opts)
|
|
136
178
|
|
|
137
179
|
if ns_item.nil?
|
|
138
180
|
fail NetSuite::RecordNotFound, "item with ID #{ns_item_internal_id} not found"
|
|
@@ -196,7 +238,11 @@ module NetSuite
|
|
|
196
238
|
field_name = 'email'
|
|
197
239
|
end
|
|
198
240
|
|
|
199
|
-
field_name ||= '
|
|
241
|
+
field_name ||= if record.to_s.end_with?('Item')
|
|
242
|
+
'displayName'
|
|
243
|
+
else
|
|
244
|
+
'name'
|
|
245
|
+
end
|
|
200
246
|
|
|
201
247
|
# TODO remove backoff when it's built-in to search
|
|
202
248
|
search = backoff { record.search({
|
|
@@ -217,25 +263,41 @@ module NetSuite
|
|
|
217
263
|
nil
|
|
218
264
|
end
|
|
219
265
|
|
|
220
|
-
def data_center_url(*args)
|
|
221
|
-
DataCenter.get(*args)
|
|
222
|
-
end
|
|
223
|
-
|
|
224
266
|
# http://mikebian.co/notes-on-dates-timezones-with-netsuites-suitetalk-api/
|
|
267
|
+
# https://wyeworks.com/blog/2016/6/22/behavior-changes-in-ruby-2.4
|
|
268
|
+
# https://github.com/rails/rails/commit/c9c5788a527b70d7f983e2b4b47e3afd863d9f48
|
|
269
|
+
|
|
225
270
|
# assumes UTC0 unix timestamp
|
|
226
271
|
def normalize_time_to_netsuite_date(unix_timestamp)
|
|
227
272
|
# convert to date to eliminate hr/min/sec
|
|
228
|
-
time = Time.at(unix_timestamp).
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
273
|
+
time = Time.at(unix_timestamp).
|
|
274
|
+
utc.
|
|
275
|
+
to_date.
|
|
276
|
+
to_datetime
|
|
277
|
+
|
|
278
|
+
# tzinfo allows us to determine the dst status of the time being passed in
|
|
279
|
+
# NetSuite requires that the time be passed to the API with the PDT TZ offset
|
|
280
|
+
# of the time passed in (i.e. not the current TZ offset of PDT)
|
|
281
|
+
|
|
282
|
+
if defined?(TZInfo)
|
|
283
|
+
# if no version is defined, less than 2.0
|
|
284
|
+
# https://github.com/tzinfo/tzinfo/blob/master/CHANGES.md#added
|
|
285
|
+
if !defined?(TZInfo::VERSION)
|
|
286
|
+
# https://stackoverflow.com/questions/2927111/ruby-get-time-in-given-timezone
|
|
287
|
+
offset = TZInfo::Timezone.get("America/Los_Angeles").period_for_utc(time).utc_total_offset_rational
|
|
288
|
+
time = time.new_offset(offset)
|
|
289
|
+
else
|
|
290
|
+
time = TZInfo::Timezone.get("America/Los_Angeles").utc_to_local(time)
|
|
291
|
+
offset = time.offset
|
|
292
|
+
end
|
|
293
|
+
else
|
|
294
|
+
# if tzinfo is not installed, let's give it our best guess: -7
|
|
295
|
+
offset = Rational(-7, 24)
|
|
235
296
|
time = time.new_offset("-07:00")
|
|
236
297
|
end
|
|
237
298
|
|
|
238
|
-
(time +
|
|
299
|
+
time = (time + (offset * -1))
|
|
300
|
+
time.iso8601
|
|
239
301
|
end
|
|
240
302
|
|
|
241
303
|
end
|
data/lib/netsuite/version.rb
CHANGED
data/netsuite.gemspec
CHANGED
|
@@ -2,8 +2,9 @@
|
|
|
2
2
|
require File.expand_path('../lib/netsuite/version', __FILE__)
|
|
3
3
|
|
|
4
4
|
Gem::Specification.new do |gem|
|
|
5
|
+
gem.licenses = ['MIT']
|
|
5
6
|
gem.authors = ['Ryan Moran', 'Michael Bianco']
|
|
6
|
-
gem.email = ['ryan.moran@gmail.com', 'mike@
|
|
7
|
+
gem.email = ['ryan.moran@gmail.com', 'mike@mikebian.co']
|
|
7
8
|
gem.description = %q{NetSuite SuiteTalk API Wrapper}
|
|
8
9
|
gem.summary = %q{NetSuite SuiteTalk API (SOAP) Wrapper}
|
|
9
10
|
gem.homepage = 'https://github.com/NetSweet/netsuite'
|
|
@@ -15,7 +16,7 @@ Gem::Specification.new do |gem|
|
|
|
15
16
|
gem.require_paths = ['lib']
|
|
16
17
|
gem.version = NetSuite::VERSION
|
|
17
18
|
|
|
18
|
-
gem.add_dependency 'savon', '>= 2.3.0'
|
|
19
|
+
gem.add_dependency 'savon', '>= 2.3.0', '<= 2.11.1'
|
|
19
20
|
|
|
20
|
-
gem.add_development_dependency 'rspec', '~> 3.
|
|
21
|
+
gem.add_development_dependency 'rspec', '~> 3.8.0'
|
|
21
22
|
end
|
|
@@ -41,4 +41,27 @@ describe NetSuite::Actions::Login do
|
|
|
41
41
|
role: 234
|
|
42
42
|
}) }.to raise_error(Savon::SOAPFault)
|
|
43
43
|
end
|
|
44
|
+
|
|
45
|
+
it 'handles a login call when token based auth is in place' do
|
|
46
|
+
NetSuite.configure do
|
|
47
|
+
consumer_key '123'
|
|
48
|
+
consumer_secret '123'
|
|
49
|
+
token_id '123'
|
|
50
|
+
token_secret '123'
|
|
51
|
+
|
|
52
|
+
api_version '2017_2'
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
message = {"platformMsgs:passport"=>{"platformCore:email"=>"email", "platformCore:password"=>"password", "platformCore:account"=>"1234", "platformCore:role"=>234}}
|
|
56
|
+
savon.expects(:login).with(:message => message).returns(File.read('spec/support/fixtures/login/success.xml'))
|
|
57
|
+
|
|
58
|
+
result = NetSuite::Actions::Login.call({
|
|
59
|
+
email: 'email',
|
|
60
|
+
password: 'password',
|
|
61
|
+
role: 234
|
|
62
|
+
})
|
|
63
|
+
|
|
64
|
+
expect(result.success?).to eq(true)
|
|
65
|
+
expect(result.body[:user_id]).to_not be_nil
|
|
66
|
+
end
|
|
44
67
|
end
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
require 'spec_helper'
|
|
2
|
+
|
|
3
|
+
describe NetSuite::Actions::UpdateList do
|
|
4
|
+
before { savon.mock! }
|
|
5
|
+
after { savon.unmock! }
|
|
6
|
+
|
|
7
|
+
context 'Items' do
|
|
8
|
+
context 'one item' do
|
|
9
|
+
let(:item) do
|
|
10
|
+
[
|
|
11
|
+
NetSuite::Records::InventoryItem.new(internal_id: '624113', item_id: 'Target', upccode: 'Target')
|
|
12
|
+
]
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
before do
|
|
16
|
+
savon.expects(:update_list).with(:message =>
|
|
17
|
+
{
|
|
18
|
+
'record' => [{
|
|
19
|
+
'listAcct:itemId' => 'Target',
|
|
20
|
+
'@xsi:type' => 'listAcct:InventoryItem',
|
|
21
|
+
'@internalId' => '624113'
|
|
22
|
+
}]
|
|
23
|
+
}).returns(File.read('spec/support/fixtures/update_list/update_list_one_item.xml'))
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
it 'makes a valid request to the NetSuite API' do
|
|
27
|
+
NetSuite::Actions::UpdateList.call(item)
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
it 'returns a valid Response object' do
|
|
31
|
+
response = NetSuite::Actions::UpdateList.call(item)
|
|
32
|
+
expect(response).to be_kind_of(NetSuite::Response)
|
|
33
|
+
expect(response).to be_success
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
context 'two items' do
|
|
38
|
+
let(:items) do
|
|
39
|
+
[
|
|
40
|
+
NetSuite::Records::InventoryItem.new(internal_id: '624172', item_id: 'Shutter Fly', upccode: 'Shutter Fly, Inc.'),
|
|
41
|
+
NetSuite::Records::InventoryItem.new(internal_id: '624113', item_id: 'Target', upccode: 'Target')
|
|
42
|
+
]
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
before do
|
|
46
|
+
savon.expects(:update_list).with(:message =>
|
|
47
|
+
{
|
|
48
|
+
'record' => [{
|
|
49
|
+
'listAcct:itemId' => 'Shutter Fly',
|
|
50
|
+
'@xsi:type' => 'listAcct:InventoryItem',
|
|
51
|
+
'@internalId' => '624172'
|
|
52
|
+
},
|
|
53
|
+
{
|
|
54
|
+
'listAcct:itemId' => 'Target',
|
|
55
|
+
'@xsi:type' => 'listAcct:InventoryItem',
|
|
56
|
+
'@internalId' => '624113'
|
|
57
|
+
}
|
|
58
|
+
]
|
|
59
|
+
}).returns(File.read('spec/support/fixtures/update_list/update_list_items.xml'))
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
it 'makes a valid request to the NetSuite API' do
|
|
63
|
+
NetSuite::Actions::UpdateList.call(items)
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
it 'returns a valid Response object' do
|
|
67
|
+
response = NetSuite::Actions::UpdateList.call(items)
|
|
68
|
+
expect(response).to be_kind_of(NetSuite::Response)
|
|
69
|
+
expect(response).to be_success
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
context 'with errors' do
|
|
75
|
+
let(:items) do
|
|
76
|
+
[
|
|
77
|
+
NetSuite::Records::InventoryItem.new(internal_id: '624172-bad', item_id: 'Shutter Fly', upccode: 'Shutter Fly, Inc.'),
|
|
78
|
+
NetSuite::Records::InventoryItem.new(internal_id: '624113-bad', item_id: 'Target', upccode: 'Target')
|
|
79
|
+
]
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
before do
|
|
83
|
+
savon.expects(:update_list).with(:message =>
|
|
84
|
+
{
|
|
85
|
+
'record' => [{
|
|
86
|
+
'listAcct:itemId' => 'Shutter Fly',
|
|
87
|
+
'@xsi:type' => 'listAcct:InventoryItem',
|
|
88
|
+
'@internalId' => '624172-bad'
|
|
89
|
+
},
|
|
90
|
+
{
|
|
91
|
+
'listAcct:itemId' => 'Target',
|
|
92
|
+
'@xsi:type' => 'listAcct:InventoryItem',
|
|
93
|
+
'@internalId' => '624113-bad'
|
|
94
|
+
}
|
|
95
|
+
]
|
|
96
|
+
}).returns(File.read('spec/support/fixtures/update_list/update_list_with_errors.xml'))
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
it 'constructs error objects' do
|
|
100
|
+
response = NetSuite::Actions::UpdateList.call(items)
|
|
101
|
+
expect(response.errors.keys).to match_array(['624172', '624113'])
|
|
102
|
+
expect(response.errors['624172'].first.code).to eq('USER_ERROR')
|
|
103
|
+
expect(response.errors['624172'].first.message).to eq('Please enter value(s) for: ItemId')
|
|
104
|
+
expect(response.errors['624172'].first.type).to eq('ERROR')
|
|
105
|
+
end
|
|
106
|
+
end
|
|
107
|
+
end
|
|
@@ -19,6 +19,48 @@ describe NetSuite::Actions::Update do
|
|
|
19
19
|
}
|
|
20
20
|
end
|
|
21
21
|
|
|
22
|
+
describe 'updating the external ID' do
|
|
23
|
+
let(:response) { NetSuite::Response.new(:success => true, :body => { :internal_id => '1' }) }
|
|
24
|
+
|
|
25
|
+
# https://github.com/NetSweet/netsuite/pull/416
|
|
26
|
+
# if the external ID is set, and the external ID field is ommitted on an update, the external ID field does not change
|
|
27
|
+
# if the external_id field is set to nil, it should not be passed to netsuite
|
|
28
|
+
# passing an empty string to the external ID field does not remove it
|
|
29
|
+
|
|
30
|
+
it 'does not pass the external ID to an update call if not modified or included in update options' do
|
|
31
|
+
expect(NetSuite::Actions::Update).to receive(:call).
|
|
32
|
+
with([customer.class, {}], {}).
|
|
33
|
+
and_return(response)
|
|
34
|
+
|
|
35
|
+
expect(customer.update).to be_truthy
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
it 'should update the external ID when the attribute on the record is set' do
|
|
39
|
+
expect(NetSuite::Actions::Update).to receive(:call).
|
|
40
|
+
with([customer.class, {external_id: 'foo'}], {}).
|
|
41
|
+
and_return(response)
|
|
42
|
+
|
|
43
|
+
customer.external_id = 'foo'
|
|
44
|
+
expect(customer.update).to be_truthy
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
it 'should update the external ID to nil when the attribute on the record is set' do
|
|
48
|
+
expect(NetSuite::Actions::Update).to receive(:call).
|
|
49
|
+
with([customer.class, {external_id: nil}], {}).
|
|
50
|
+
and_return(response)
|
|
51
|
+
|
|
52
|
+
expect(customer.update(external_id: nil)).to be_truthy
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
it 'should update the external ID to the options value not the attribute value' do
|
|
56
|
+
expect(NetSuite::Actions::Update).to receive(:call).
|
|
57
|
+
with([customer.class, {external_id: 'bar'}], {}).
|
|
58
|
+
and_return(response)
|
|
59
|
+
|
|
60
|
+
customer.external_id = 'foo'
|
|
61
|
+
expect(customer.update(external_id: 'bar')).to be_truthy
|
|
62
|
+
end
|
|
63
|
+
end
|
|
22
64
|
|
|
23
65
|
context 'when successful' do
|
|
24
66
|
|
|
@@ -29,10 +29,12 @@ describe NetSuite::Configuration do
|
|
|
29
29
|
end
|
|
30
30
|
|
|
31
31
|
describe '#connection' do
|
|
32
|
+
EXAMPLE_ENDPOINT = 'https://1023.suitetalk.api.netsuite.com/services/NetSuitePort_2020_2'
|
|
32
33
|
before(:each) do
|
|
33
34
|
# reset clears out the password info
|
|
34
35
|
config.email 'me@example.com'
|
|
35
36
|
config.password 'me@example.com'
|
|
37
|
+
config.endpoint EXAMPLE_ENDPOINT
|
|
36
38
|
config.account 1023
|
|
37
39
|
config.wsdl "my_wsdl"
|
|
38
40
|
config.api_version "2012_2"
|
|
@@ -57,6 +59,19 @@ describe NetSuite::Configuration do
|
|
|
57
59
|
|
|
58
60
|
expect(config).to have_received(:cached_wsdl)
|
|
59
61
|
end
|
|
62
|
+
|
|
63
|
+
it 'sets the endpoint on the Savon client' do
|
|
64
|
+
# this is ugly/brittle, but it's hard to see how else to test this
|
|
65
|
+
savon_configs = config.connection.globals.instance_eval {@options}
|
|
66
|
+
expect(savon_configs.fetch(:endpoint)).to eq(EXAMPLE_ENDPOINT)
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
it 'handles a nil endpoint' do
|
|
70
|
+
config.endpoint = nil
|
|
71
|
+
# this is ugly/brittle, but it's hard to see how else to test this
|
|
72
|
+
savon_configs = config.connection.globals.instance_eval {@options}
|
|
73
|
+
expect(savon_configs.fetch(:endpoint)).to eq(nil)
|
|
74
|
+
end
|
|
60
75
|
end
|
|
61
76
|
|
|
62
77
|
describe '#wsdl' do
|
|
@@ -166,6 +181,23 @@ describe NetSuite::Configuration do
|
|
|
166
181
|
end
|
|
167
182
|
end
|
|
168
183
|
|
|
184
|
+
describe '#endpoint' do
|
|
185
|
+
it 'can be set with endpoint=' do
|
|
186
|
+
config.endpoint = 42
|
|
187
|
+
expect(config.endpoint).to eq(42)
|
|
188
|
+
end
|
|
189
|
+
|
|
190
|
+
it 'can be set with just endpoint(value)' do
|
|
191
|
+
config.endpoint(42)
|
|
192
|
+
expect(config.endpoint).to eq(42)
|
|
193
|
+
end
|
|
194
|
+
|
|
195
|
+
it 'supports nil endpoints' do
|
|
196
|
+
config.endpoint = nil
|
|
197
|
+
expect(config.endpoint).to eq(nil)
|
|
198
|
+
end
|
|
199
|
+
end
|
|
200
|
+
|
|
169
201
|
describe '#auth_header' do
|
|
170
202
|
context 'when doing user authentication' do
|
|
171
203
|
before do
|
|
@@ -326,15 +358,11 @@ describe NetSuite::Configuration do
|
|
|
326
358
|
|
|
327
359
|
describe "#credentials" do
|
|
328
360
|
context "when none are defined" do
|
|
329
|
-
skip "should properly create the auth credentials"
|
|
330
|
-
|
|
331
|
-
end
|
|
361
|
+
skip "should properly create the auth credentials"
|
|
332
362
|
end
|
|
333
363
|
|
|
334
364
|
context "when they are defined" do
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
end
|
|
365
|
+
skip "should properly replace the default auth credentials"
|
|
338
366
|
end
|
|
339
367
|
end
|
|
340
368
|
|
|
@@ -371,4 +399,81 @@ describe NetSuite::Configuration do
|
|
|
371
399
|
end
|
|
372
400
|
end
|
|
373
401
|
|
|
402
|
+
describe "#log" do
|
|
403
|
+
it 'allows a file path to be set as the log destination' do
|
|
404
|
+
file_path = Tempfile.new.path
|
|
405
|
+
config.log = file_path
|
|
406
|
+
config.logger.info "foo"
|
|
407
|
+
|
|
408
|
+
log_contents = open(file_path).read
|
|
409
|
+
expect(log_contents).to include("foo")
|
|
410
|
+
end
|
|
411
|
+
|
|
412
|
+
it 'allows an IO device to bet set as the log destination' do
|
|
413
|
+
stream = StringIO.new
|
|
414
|
+
config.log = stream
|
|
415
|
+
config.logger.info "foo"
|
|
416
|
+
|
|
417
|
+
expect(stream.string).to include("foo")
|
|
418
|
+
end
|
|
419
|
+
end
|
|
420
|
+
|
|
421
|
+
describe '#log_level' do
|
|
422
|
+
it 'defaults to :debug' do
|
|
423
|
+
expect(config.log_level).to eq(:debug)
|
|
424
|
+
end
|
|
425
|
+
|
|
426
|
+
it 'can be initially set to any log level' do
|
|
427
|
+
config.log_level(:info)
|
|
428
|
+
|
|
429
|
+
expect(config.log_level).to eq(:info)
|
|
430
|
+
end
|
|
431
|
+
|
|
432
|
+
it 'can override itself' do
|
|
433
|
+
config.log_level = :info
|
|
434
|
+
|
|
435
|
+
expect(config.log_level).to eq(:info)
|
|
436
|
+
|
|
437
|
+
config.log_level(:debug)
|
|
438
|
+
|
|
439
|
+
expect(config.log_level).to eq(:debug)
|
|
440
|
+
end
|
|
441
|
+
end
|
|
442
|
+
|
|
443
|
+
describe '#log_level=' do
|
|
444
|
+
it 'can set the initial log_level' do
|
|
445
|
+
config.log_level = :info
|
|
446
|
+
|
|
447
|
+
expect(config.log_level).to eq(:info)
|
|
448
|
+
end
|
|
449
|
+
|
|
450
|
+
it 'can override a previously set log level' do
|
|
451
|
+
config.log_level = :info
|
|
452
|
+
|
|
453
|
+
expect(config.log_level).to eq(:info)
|
|
454
|
+
|
|
455
|
+
config.log_level = :debug
|
|
456
|
+
|
|
457
|
+
expect(config.log_level).to eq(:debug)
|
|
458
|
+
end
|
|
459
|
+
end
|
|
460
|
+
|
|
461
|
+
describe 'timeouts' do
|
|
462
|
+
it 'has defaults' do
|
|
463
|
+
expect(config.read_timeout).to eql(60)
|
|
464
|
+
expect(config.open_timeout).to be_nil
|
|
465
|
+
end
|
|
466
|
+
|
|
467
|
+
it 'sets timeouts' do
|
|
468
|
+
config.read_timeout = 100
|
|
469
|
+
config.open_timeout = 60
|
|
470
|
+
|
|
471
|
+
expect(config.read_timeout).to eql(100)
|
|
472
|
+
expect(config.open_timeout).to eql(60)
|
|
473
|
+
|
|
474
|
+
# ensure no exception is raised
|
|
475
|
+
config.connection
|
|
476
|
+
end
|
|
477
|
+
end
|
|
478
|
+
|
|
374
479
|
end
|