solidus_bactracs 3.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 (79) hide show
  1. checksums.yaml +7 -0
  2. data/.bundle/config +2 -0
  3. data/.circleci/config.yml +41 -0
  4. data/.gem_release.yml +5 -0
  5. data/.github/stale.yml +17 -0
  6. data/.github_changelog_generator +2 -0
  7. data/.gitignore +22 -0
  8. data/.rspec +2 -0
  9. data/.rubocop.yml +14 -0
  10. data/.rubocop_todo.yml +40 -0
  11. data/CHANGELOG.md +16 -0
  12. data/Gemfile +33 -0
  13. data/LICENSE +26 -0
  14. data/README.md +216 -0
  15. data/Rakefile +6 -0
  16. data/app/assets/javascripts/spree/backend/solidus_backtracs.js +2 -0
  17. data/app/assets/javascripts/spree/frontend/solidus_backtracs.js +2 -0
  18. data/app/assets/stylesheets/spree/backend/solidus_backtracs.css +4 -0
  19. data/app/assets/stylesheets/spree/frontend/solidus_backtracs.css +4 -0
  20. data/app/controllers/spree/backtracs_controller.rb +46 -0
  21. data/app/decorators/models/solidus_backtracs/spree/shipment_decorator.rb +33 -0
  22. data/app/helpers/solidus_backtracs/export_helper.rb +60 -0
  23. data/app/jobs/solidus_backtracs/api/schedule_shipment_syncs_job.rb +36 -0
  24. data/app/jobs/solidus_backtracs/api/sync_shipment_job.rb +17 -0
  25. data/app/jobs/solidus_backtracs/api/sync_shipments_job.rb +41 -0
  26. data/app/queries/solidus_backtracs/shipment/between_query.rb +14 -0
  27. data/app/queries/solidus_backtracs/shipment/exportable_query.rb +24 -0
  28. data/app/queries/solidus_backtracs/shipment/pending_api_sync_query.rb +51 -0
  29. data/app/views/spree/backtracs/export.xml.builder +58 -0
  30. data/bin/console +17 -0
  31. data/bin/rails +7 -0
  32. data/bin/rails-engine +13 -0
  33. data/bin/rails-sandbox +16 -0
  34. data/bin/rake +7 -0
  35. data/bin/sandbox +86 -0
  36. data/bin/setup +8 -0
  37. data/config/locales/en.yml +5 -0
  38. data/config/routes.rb +6 -0
  39. data/db/migrate/20210220093010_add_backtracs_api_sync_fields.rb +8 -0
  40. data/lib/generators/solidus_backtracs/install/install_generator.rb +27 -0
  41. data/lib/generators/solidus_backtracs/install/templates/initializer.rb +92 -0
  42. data/lib/solidus_backtracs/api/batch_syncer.rb +45 -0
  43. data/lib/solidus_backtracs/api/client.rb +36 -0
  44. data/lib/solidus_backtracs/api/rate_limited_error.rb +23 -0
  45. data/lib/solidus_backtracs/api/request_error.rb +33 -0
  46. data/lib/solidus_backtracs/api/request_runner.rb +109 -0
  47. data/lib/solidus_backtracs/api/shipment_serializer.rb +103 -0
  48. data/lib/solidus_backtracs/api/threshold_verifier.rb +28 -0
  49. data/lib/solidus_backtracs/configuration.rb +63 -0
  50. data/lib/solidus_backtracs/engine.rb +19 -0
  51. data/lib/solidus_backtracs/errors.rb +23 -0
  52. data/lib/solidus_backtracs/shipment_notice.rb +58 -0
  53. data/lib/solidus_backtracs/testing_support/factories.rb +4 -0
  54. data/lib/solidus_backtracs/version.rb +5 -0
  55. data/lib/solidus_backtracs.rb +16 -0
  56. data/solidus_bactracs.gemspec +39 -0
  57. data/spec/controllers/spree/backtracs_controller_spec.rb +103 -0
  58. data/spec/fixtures/backtracs_xml_schema.xsd +171 -0
  59. data/spec/jobs/solidus_backtracs/api/schedule_shipment_syncs_job_spec.rb +32 -0
  60. data/spec/jobs/solidus_backtracs/api/sync_shipments_job_spec.rb +102 -0
  61. data/spec/lib/solidus_backtracs/api/batch_syncer_spec.rb +228 -0
  62. data/spec/lib/solidus_backtracs/api/client_spec.rb +120 -0
  63. data/spec/lib/solidus_backtracs/api/rate_limited_error_spec.rb +21 -0
  64. data/spec/lib/solidus_backtracs/api/request_error_spec.rb +20 -0
  65. data/spec/lib/solidus_backtracs/api/request_runner_spec.rb +65 -0
  66. data/spec/lib/solidus_backtracs/api/shipment_serializer_spec.rb +25 -0
  67. data/spec/lib/solidus_backtracs/api/threshold_verifier_spec.rb +61 -0
  68. data/spec/lib/solidus_backtracs/shipment_notice_spec.rb +111 -0
  69. data/spec/lib/solidus_backtracs_spec.rb +9 -0
  70. data/spec/models/spree/shipment_spec.rb +49 -0
  71. data/spec/queries/solidus_backtracs/shipment/between_query_spec.rb +53 -0
  72. data/spec/queries/solidus_backtracs/shipment/exportable_query_spec.rb +53 -0
  73. data/spec/queries/solidus_backtracs/shipment/pending_api_sync_query_spec.rb +37 -0
  74. data/spec/spec_helper.rb +31 -0
  75. data/spec/support/configuration_helper.rb +13 -0
  76. data/spec/support/controllers.rb +1 -0
  77. data/spec/support/webmock.rb +3 -0
  78. data/spec/support/xsd.rb +5 -0
  79. metadata +249 -0
@@ -0,0 +1,109 @@
1
+ # frozen_string_literal: true
2
+ require "uri"
3
+ require "net/http"
4
+
5
+ module SolidusBacktracs
6
+ module Api
7
+ class RequestRunner
8
+
9
+ def initialize
10
+ @username = SolidusBacktracs.configuration.authentication_username
11
+ @password = SolidusBacktracs.configuration.authentication_password
12
+ @api_base = SolidusBacktracs.configuration.api_base
13
+ @retries = SolidusBacktracs.configuration.api_retries
14
+ end
15
+
16
+ def authenticated_call(method: nil, path: nil, serializer: nil, shipment: nil, count: 0)
17
+ if count <= @retries
18
+ unless @username.present? || @password.present? || @api_base.present?
19
+ raise "Credentials not defined for Authentication"
20
+ end
21
+
22
+ @response = Rails.cache.fetch("backtracks_cache_key", expires_in: 1.hour, skip_nil: true) do
23
+ self.call(method: :get, path: "/webservices/user/Authentication.asmx/Login?sUserName=#{@username}&sPassword=#{@password}")
24
+ end
25
+
26
+ raise RequestError.from_response(@response) unless @response # just try again for @retries?
27
+ authenticted = parse_authentication_response(@response, "Result")
28
+ raise RequestError.from_response(@response) if authenticted == "false"
29
+ sguid = parse_authentication_response(@response, "Message")
30
+
31
+ if authenticted == 'false'
32
+ clear_cache
33
+ raise "User Not Authenticated"
34
+ else
35
+ sguid = parse_authentication_response(@response, "Message")
36
+ params = serializer.call(shipment, sguid)
37
+
38
+ rma_response = call(method: :post, path: path, params: params)
39
+ unless parse_rma_creation_response(rma_response) == 'true'
40
+ clear_cache
41
+ count += 1
42
+ self.authenticated_call(method: :post, path: '/webservices/rma/rmaservice.asmx', serializer: serializer, shipment: shipment, count: count)
43
+ end
44
+ shipment_synced(shipment)
45
+ end
46
+ else
47
+ shipment_sync_failed(shipment)
48
+ end
49
+ end
50
+
51
+ def call(method: nil, path: nil, params: {})
52
+ doc = {}
53
+ if params.present?
54
+ doc = Nokogiri::XML(params.to_s)
55
+ end
56
+ response = HTTParty.send(
57
+ method,
58
+ URI.join(@api_base, path),
59
+ body: doc.to_xml,
60
+ http_proxyaddr: SolidusBacktracs.configuration.proxy_address,
61
+ http_proxyport: SolidusBacktracs.configuration.proxy_port,
62
+ http_proxyuser: SolidusBacktracs.configuration.proxy_username,
63
+ http_proxypass: SolidusBacktracs.configuration.proxy_password,
64
+ headers: {
65
+ 'Content-Type' => 'text/xml',
66
+ },
67
+ )
68
+
69
+ case response.code.to_s
70
+ when /2\d{2}/
71
+ response
72
+ when '429'
73
+ raise RateLimitedError.from_response(response)
74
+ else
75
+ raise RequestError.from_response(response)
76
+ end
77
+ end
78
+
79
+ def clear_cache
80
+ Rails.cache.delete('backtracks_cache_key')
81
+ @response = nil
82
+ end
83
+
84
+ def parse_authentication_response(response, type)
85
+ response.dig("AuthenticationResponse", type)
86
+ end
87
+
88
+ def parse_rma_creation_response(response)
89
+ response.dig("Envelope", "Body", "CreateNewResponse", "CreateNewResult", "Result")
90
+ end
91
+
92
+ def shipment_synced(shipment)
93
+ shipment.update_column(:backtracs_synced_at, Time.zone.now)
94
+
95
+ ::Spree::Event.fire(
96
+ 'solidus_backtracs.api.sync_completed',
97
+ shipment: shipment
98
+ )
99
+ end
100
+
101
+ def shipment_sync_failed(shipment)
102
+ ::Spree::Event.fire(
103
+ 'solidus_backtracs.api.sync_failed',
104
+ shipment: shipment
105
+ )
106
+ end
107
+ end
108
+ end
109
+ end
@@ -0,0 +1,103 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SolidusBacktracs
4
+ module Api
5
+ class ShipmentSerializer
6
+
7
+ def initialize(shipment:)
8
+ @shipment = shipment
9
+ @config = SolidusBacktracs.config
10
+ @property_id = ::Spree::Property.find_by(name: SolidusBacktracs.config.default_property_name)&.id
11
+ end
12
+
13
+ def call(sguid: nil)
14
+ order = @shipment.order
15
+ user = @shipment.user
16
+
17
+ xml = Builder::XmlMarkup.new
18
+ xml.instruct!(:xml, :encoding => "UTF-8")
19
+
20
+ xml.soap(:Envelope, {"xmlns:xsi" => "http://www.w3.org/2001/XMLSchema-instance", "xmlns:xsd" => "http://www.w3.org/2001/XMLSchema", "xmlns:soap" => "http://schemas.xmlsoap.org/soap/envelope/"}) do
21
+ xml.soap :Body do
22
+ xml.CreateNew({"xmlns" => "http://bactracs.andlor.com/rmaservice"}) do
23
+ xml.sGuid sguid
24
+ xml.NewRMA {
25
+ xml.RMANumber @shipment.number
26
+ xml.RMATypeName @config.default_rma_type
27
+ xml.RMASubTypeName
28
+ xml.CustomerRef
29
+ xml.InboundShippingPriority
30
+ xml.InboundTrackingNumber @shipment.tracking
31
+
32
+ xml.Ship {
33
+ xml.Carrier @config.default_carrier
34
+ xml.ShipMethod @config.default_ship_method
35
+ xml.ShipDate @shipment.created_at.strftime(SolidusBacktracs::ExportHelper::BACTRACS_DATE_FORMAT)
36
+ xml.TrackingNumber @shipment.tracking
37
+ xml.SerialNumber @shipment.number
38
+ xml.Ud1
39
+ }
40
+ xml.Customer {
41
+ SolidusBacktracs::ExportHelper.backtracs_address(xml, order, :ship)
42
+ SolidusBacktracs::ExportHelper.backtracs_address(xml, order, :bill)
43
+ }
44
+ xml.Rep {
45
+ xml.Code
46
+ xml.Name user.full_name
47
+ xml.Email user.email
48
+ }
49
+
50
+ xml.RMALines {
51
+ @shipment.line_items.each do |line|
52
+ product = line.product
53
+ if product.respond_to?(:assembly?) && product.assembly?
54
+ product.parts.each do |part|
55
+ next unless part.product.product_properties.where(property_id: @property_id).present?
56
+ line_items_xml(xml: xml, line_item: line, variant: part, order: order)
57
+ end
58
+ else
59
+ line_items_xml(xml: xml, line_item: line, variant: line.variant, order: order)
60
+ end
61
+ end
62
+ }
63
+ xml.OrderDate order.completed_at.strftime(SolidusBacktracs::ExportHelper::BACTRACS_DATE_FORMAT)
64
+ xml.CreateDate @shipment.created_at.strftime(SolidusBacktracs::ExportHelper::BACTRACS_DATE_FORMAT)
65
+ xml.Status @config.default_status
66
+ xml.RMAId @shipment.id
67
+ xml.ClientGuid
68
+ }
69
+ end
70
+ end
71
+ end
72
+ xml
73
+ end
74
+
75
+ def line_items_xml(xml: nil, line_item: nil, variant: nil, order: nil)
76
+ shipment_notice = @shipment.shipment_notice
77
+ xml.RMALine {
78
+ xml.DFItem find_sku_variant(variant)
79
+ xml.DFModelNum find_sku_variant(variant)
80
+ xml.DFCategory
81
+ xml.DFCategoryDescription
82
+ xml.DFQuantity line_item.quantity
83
+ xml.DFUnitPrice line_item.price
84
+ xml.DFSerialNumbers
85
+ xml.Ud1s
86
+ xml.CurrentWarranties
87
+ xml.DFComments
88
+ xml.DFStatus @shipment.state
89
+ xml.PurchaseDate order.completed_at.strftime(SolidusBacktracs::ExportHelper::BACTRACS_DATE_FORMAT)
90
+ xml.ServiceProvider shipment_notice&.service
91
+ xml.WarrantyRepair
92
+ xml.RMALineTest
93
+ xml.InboundShipWeight variant.weight.to_f
94
+ xml.RPLocation @config.default_rp_location
95
+ }
96
+ end
97
+
98
+ def find_sku_variant(variant)
99
+ @config.sku_map[variant.sku].present? ? @config.sku_map[variant.sku] : variant.sku
100
+ end
101
+ end
102
+ end
103
+ end
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SolidusBacktracs
4
+ module Api
5
+ class ThresholdVerifier
6
+ class << self
7
+ def call(shipment)
8
+ return false unless shipment.order.completed?
9
+
10
+ !!(shipment_requires_creation?(shipment) || shipment_requires_update?(shipment))
11
+ end
12
+
13
+ private
14
+
15
+ def shipment_requires_creation?(shipment)
16
+ shipment.backtracs_synced_at.nil? &&
17
+ Time.zone.now - shipment.order.updated_at < SolidusBacktracs.config.api_sync_threshold
18
+ end
19
+
20
+ def shipment_requires_update?(shipment)
21
+ shipment.backtracs_synced_at &&
22
+ shipment.backtracs_synced_at < shipment.order.updated_at &&
23
+ Time.zone.now - shipment.order.updated_at < SolidusBacktracs.config.api_sync_threshold
24
+ end
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,63 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SolidusBacktracs
4
+ class Configuration
5
+ attr_accessor(
6
+ :webhook_username,
7
+ :webhook_password,
8
+ :weight_units,
9
+ :ssl_encrypted,
10
+ :capture_at_notification,
11
+ :export_canceled_shipments,
12
+ :api_batch_size,
13
+ :api_sync_threshold,
14
+ :api_shipment_serializer,
15
+ :api_key,
16
+ :api_secret,
17
+ :api_shipment_matcher,
18
+ :error_handler,
19
+ :shipment_notice_class,
20
+ :authentication_username,
21
+ :authentication_password,
22
+ :api_base,
23
+ :api_retries,
24
+ :proxy_address,
25
+ :proxy_port,
26
+ :proxy_username,
27
+ :proxy_password,
28
+ :default_carrier,
29
+ :default_ship_method,
30
+ :default_rp_location,
31
+ :default_status,
32
+ :default_property_name,
33
+ :sku_map,
34
+ :default_rma_type,
35
+ :shippable_skus
36
+ )
37
+
38
+ def initialize
39
+ @api_batch_size = 100
40
+ @api_sync_threshold = 7.days
41
+ @error_handler = ->(_error, _extra = {}) {
42
+ Rails.logger.error "#{error.inspect} (#{extra.inspect})"
43
+ }
44
+ @api_shipment_matcher = proc do |backtracs_order, shipments|
45
+ shipments.find { |shipment| shipment.number == backtracs_order['orderNumber'] }
46
+ end
47
+
48
+ @shipment_notice_class = 'SolidusBacktracs::ShipmentNotice'
49
+ end
50
+ end
51
+
52
+ class << self
53
+ def configuration
54
+ @configuration ||= Configuration.new
55
+ end
56
+
57
+ alias config configuration
58
+
59
+ def configure
60
+ yield configuration
61
+ end
62
+ end
63
+ end
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'solidus_core'
4
+ require 'solidus_support'
5
+
6
+ module SolidusBacktracs
7
+ class Engine < Rails::Engine
8
+ include SolidusSupport::EngineExtensions
9
+
10
+ isolate_namespace ::Spree
11
+
12
+ engine_name 'solidus_backtracs'
13
+
14
+ # use rspec for tests
15
+ config.generators do |g|
16
+ g.test_framework :rspec
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SolidusBacktracs
4
+ class Error < StandardError; end
5
+
6
+ class OrderNotPaidError < Error
7
+ def initialize(order, *args)
8
+ super("Order #{order.number} is not paid and capture_at_notification is false", *args)
9
+ end
10
+ end
11
+
12
+ class ShipmentNotFoundError < Error
13
+ def initialize(shipment_number, *args)
14
+ super("Could not find shipment with number #{shipment_number}", *args)
15
+ end
16
+ end
17
+
18
+ class PaymentError < Error
19
+ def initialize(payment, *args)
20
+ super("Could not process payment #{payment.id}", *args)
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,58 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SolidusBacktracs
4
+ class ShipmentNotice
5
+ attr_reader :shipment_number, :shipment_tracking
6
+
7
+ class << self
8
+ def from_payload(params)
9
+ new(
10
+ shipment_number: params[:order_number],
11
+ shipment_tracking: params[:tracking_number],
12
+ )
13
+ end
14
+ end
15
+
16
+ def initialize(shipment_number:, shipment_tracking:)
17
+ @shipment_number = shipment_number
18
+ @shipment_tracking = shipment_tracking
19
+ end
20
+
21
+ def apply
22
+ unless shipment
23
+ raise ShipmentNotFoundError, shipment
24
+ end
25
+
26
+ process_payment
27
+ ship_shipment
28
+
29
+ shipment
30
+ end
31
+
32
+ private
33
+
34
+ def shipment
35
+ @shipment ||= ::Spree::Shipment.find_by(number: shipment_number)
36
+ end
37
+
38
+ def process_payment
39
+ return if shipment.order.paid?
40
+
41
+ unless SolidusBacktracs.configuration.capture_at_notification
42
+ raise OrderNotPaidError, shipment.order
43
+ end
44
+
45
+ shipment.order.payments.pending.each do |payment|
46
+ payment.capture!
47
+ rescue ::Spree::Core::GatewayError
48
+ raise PaymentError, payment
49
+ end
50
+ end
51
+
52
+ def ship_shipment
53
+ shipment.update!(tracking: shipment_tracking)
54
+ shipment.ship! if shipment.can_ship?
55
+ shipment.order.recalculate
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,4 @@
1
+ # frozen_string_literal: true
2
+
3
+ FactoryBot.define do
4
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SolidusBacktracs
4
+ VERSION = '3.0.0'
5
+ end
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'httparty'
4
+
5
+ require 'solidus_backtracs/api/batch_syncer'
6
+ require 'solidus_backtracs/api/request_runner'
7
+ require 'solidus_backtracs/api/client'
8
+ require 'solidus_backtracs/api/request_error'
9
+ require 'solidus_backtracs/api/rate_limited_error'
10
+ require 'solidus_backtracs/api/shipment_serializer'
11
+ require 'solidus_backtracs/api/threshold_verifier'
12
+ require 'solidus_backtracs/configuration'
13
+ require 'solidus_backtracs/errors'
14
+ require 'solidus_backtracs/shipment_notice'
15
+ require 'solidus_backtracs/version'
16
+ require 'solidus_backtracs/engine'
@@ -0,0 +1,39 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'lib/solidus_backtracs/version'
4
+
5
+ Gem::Specification.new do |spec|
6
+ spec.name = 'solidus_bactracs'
7
+ spec.version = SolidusBacktracs::VERSION
8
+ spec.authors = ['Zeryab Ali', 'Zachary Jones']
9
+ spec.email = 'techbilling@suvie.com'
10
+
11
+ spec.summary = 'A Solidus extension for integrating the Bactracs API.'
12
+ spec.homepage = 'https://github.com/suvie-eng/solidus_bactracs'
13
+ spec.license = 'BSD-3-Clause'
14
+
15
+ spec.metadata['homepage_uri'] = spec.homepage
16
+ spec.metadata['source_code_uri'] = 'https://github.com/suvie-eng/solidus_bactracs/'
17
+ spec.metadata['changelog_uri'] = 'https://github.com/suvie-eng/solidus_bactracs/blob/master/CHANGELOG.md'
18
+
19
+ spec.required_ruby_version = Gem::Requirement.new('>= 2.5')
20
+
21
+ # Specify which files should be added to the gem when it is released.
22
+ # The `git ls-files -z` loads the files in the RubyGem that have been added into git.
23
+ files = Dir.chdir(__dir__) { `git ls-files -z`.split("\x0") }
24
+
25
+ spec.files = files.grep_v(%r{^(test|spec|features)/})
26
+ spec.test_files = files.grep(%r{^(test|spec|features)/})
27
+ spec.bindir = "exe"
28
+ spec.executables = files.grep(%r{^exe/}) { |f| File.basename(f) }
29
+ spec.require_paths = ["lib"]
30
+
31
+ spec.add_dependency 'httparty', '~> 0.18'
32
+ spec.add_dependency 'solidus_core', ['>= 2.0.0', '< 4']
33
+ spec.add_dependency 'solidus_support', '~> 0.5'
34
+
35
+ spec.add_development_dependency 'rails-controller-testing'
36
+ spec.add_development_dependency 'rspec-xsd'
37
+ spec.add_development_dependency 'solidus_dev_support', '~> 2.5'
38
+ spec.add_development_dependency 'webmock'
39
+ end
@@ -0,0 +1,103 @@
1
+ # frozen_string_literal: true
2
+
3
+ RSpec.describe Spree::BacktracsController do
4
+ render_views
5
+
6
+ describe '#export' do
7
+ context 'when the authentication is invalid' do
8
+ it 'returns an error error' do
9
+ get :export, params: { format: 'xml' }
10
+
11
+ expect(response.status).to eq(401)
12
+ end
13
+ end
14
+
15
+ context 'when the authentication is valid' do
16
+ it 'responds with 200 OK' do
17
+ stub_backtracs_auth
18
+ create(:order_ready_to_ship)
19
+
20
+ get :export,
21
+ params: {
22
+ start_date: 1.day.ago.strftime('%m/%d/%Y %H:%M'),
23
+ end_date: 1.day.from_now.strftime('%m/%d/%Y %H:%M'),
24
+ format: 'xml'
25
+ }
26
+
27
+ expect(response.status).to eq(200)
28
+ end
29
+
30
+ it 'generates Backtracs-compliant XML' do
31
+ stub_backtracs_auth
32
+ create(:order_ready_to_ship)
33
+
34
+ get :export, params: {
35
+ start_date: 1.day.ago.strftime('%m/%d/%Y %H:%M'),
36
+ end_date: 1.day.from_now.strftime('%m/%d/%Y %H:%M'),
37
+ format: 'xml'
38
+ }
39
+
40
+ expect(response.body).to pass_validation('spec/fixtures/backtracs_xml_schema.xsd')
41
+ end
42
+ end
43
+ end
44
+
45
+ describe '#shipnotify' do
46
+ context 'when the authentication is valid' do
47
+ context 'when the shipment can be found' do
48
+ it 'responds with 200 OK' do
49
+ stub_backtracs_auth
50
+ shipment = create(:order_ready_to_ship).shipments.first
51
+
52
+ post :shipnotify, params: {
53
+ order_number: shipment.number,
54
+ tracking_number: '123456',
55
+ format: 'xml',
56
+ }
57
+ shipment.reload
58
+
59
+ expect(response.status).to eq(200)
60
+ end
61
+
62
+ it 'updates the shipment' do
63
+ stub_backtracs_auth
64
+ shipment = create(:order_ready_to_ship).shipments.first
65
+
66
+ post :shipnotify, params: {
67
+ order_number: shipment.number,
68
+ tracking_number: '123456',
69
+ format: 'xml',
70
+ }
71
+ shipment.reload
72
+
73
+ expect(shipment).to have_attributes(
74
+ tracking: '123456',
75
+ state: 'shipped',
76
+ shipped_at: an_instance_of(ActiveSupport::TimeWithZone),
77
+ )
78
+ end
79
+ end
80
+
81
+ context 'when the shipment cannot be found' do
82
+ it 'responds with 400 Bad Request' do
83
+ stub_backtracs_auth
84
+ shipment = create(:order_ready_to_ship).shipments.first
85
+
86
+ post :shipnotify, params: {
87
+ order_number: 'ABC123',
88
+ tracking_number: '123456',
89
+ format: 'xml',
90
+ }
91
+ shipment.reload
92
+
93
+ expect(response.status).to eq(400)
94
+ end
95
+ end
96
+ end
97
+ end
98
+
99
+ def stub_backtracs_auth(username = 'mario', password = 'lemieux')
100
+ stub_configuration(username: username, password: password)
101
+ request.headers['Authorization'] = ActionController::HttpAuthentication::Basic.encode_credentials(username, password)
102
+ end
103
+ end