Commit 291179ed2e407c287e1359c1add0a9900164fa69

Authored by Isaac Lewis
Committed by GitHub
2 parents 9a47698f a3292c71

Merge pull request #2 from wildland/query

Query Endpoint
.rubocop.yml 0 → 100644
  1 +AllCops:
  2 + Exclude:
  3 + - Gemfile*
  4 + - syspro-ruby.gemspec
CHANGELOG.md 0 → 100644
  1 +# Changelog
  2 +All notable changes to this project will be documented in this file.
  3 +
  4 +The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/)
  5 +and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html).
  6 +
  7 +## [Unreleased]
  8 +### Added
  9 +### Changed
  10 +### Deprecated
  11 +### Removed
  12 +### Fixed
  13 +### Security
  14 +
  15 +## 0.1.0 - 2018-04-06
  16 +### Added
  17 +- CHANGELOG file
  18 +- `rubocop` gem
  19 +- `vcr` gem
  20 +- Query Endpoint
1 -source "https://rubygems.org" 1 +# frozen_string_literal: true
2 2
3 -git_source(:github) {|repo_name| "https://github.com/#{repo_name}" } 3 +source 'https://rubygems.org'
  4 +
  5 +git_source(:github) { |repo_name| "https://github.com/#{repo_name}" }
4 6
5 gemspec 7 gemspec
1 PATH 1 PATH
2 remote: . 2 remote: .
3 specs: 3 specs:
4 - syspro (0.1.0) 4 + syspro-ruby (1.0.0.alpha.1)
5 faraday (~> 0.10) 5 faraday (~> 0.10)
  6 + nokogiri (~> 1.8.2)
6 7
7 GEM 8 GEM
8 remote: https://rubygems.org/ 9 remote: https://rubygems.org/
9 specs: 10 specs:
  11 + addressable (2.5.2)
  12 + public_suffix (>= 2.0.2, < 4.0)
  13 + ast (2.4.0)
10 coderay (1.1.2) 14 coderay (1.1.2)
  15 + crack (0.4.3)
  16 + safe_yaml (~> 1.0.0)
11 faraday (0.14.0) 17 faraday (0.14.0)
12 multipart-post (>= 1.2, < 3) 18 multipart-post (>= 1.2, < 3)
  19 + hashdiff (0.3.7)
13 method_source (0.9.0) 20 method_source (0.9.0)
  21 + mini_portile2 (2.3.0)
  22 + minispec-metadata (2.0.0)
  23 + minitest
14 minitest (5.11.3) 24 minitest (5.11.3)
  25 + minitest-vcr (1.4.0)
  26 + minispec-metadata (~> 2.0)
  27 + minitest (>= 4.7.5)
  28 + vcr (>= 2.9)
15 multipart-post (2.0.0) 29 multipart-post (2.0.0)
  30 + nokogiri (1.8.2)
  31 + mini_portile2 (~> 2.3.0)
  32 + parallel (1.12.1)
  33 + parser (2.5.0.5)
  34 + ast (~> 2.4.0)
  35 + powerpack (0.1.1)
16 pry (0.11.3) 36 pry (0.11.3)
17 coderay (~> 1.1.0) 37 coderay (~> 1.1.0)
18 method_source (~> 0.9.0) 38 method_source (~> 0.9.0)
  39 + public_suffix (3.0.2)
  40 + rainbow (3.0.0)
19 rake (10.5.0) 41 rake (10.5.0)
  42 + rubocop (0.54.0)
  43 + parallel (~> 1.10)
  44 + parser (>= 2.5)
  45 + powerpack (~> 0.1)
  46 + rainbow (>= 2.2.2, < 4.0)
  47 + ruby-progressbar (~> 1.7)
  48 + unicode-display_width (~> 1.0, >= 1.0.1)
  49 + ruby-progressbar (1.9.0)
  50 + safe_yaml (1.0.4)
  51 + unicode-display_width (1.3.0)
  52 + vcr (4.0.0)
  53 + webmock (3.3.0)
  54 + addressable (>= 2.3.6)
  55 + crack (>= 0.3.2)
  56 + hashdiff
20 57
21 PLATFORMS 58 PLATFORMS
22 ruby 59 ruby
@@ -24,9 +61,12 @@ PLATFORMS @@ -24,9 +61,12 @@ PLATFORMS
24 DEPENDENCIES 61 DEPENDENCIES
25 bundler (~> 1.16) 62 bundler (~> 1.16)
26 minitest (~> 5.0) 63 minitest (~> 5.0)
  64 + minitest-vcr (~> 1.4.0)
27 pry (~> 0.11) 65 pry (~> 0.11)
28 rake (~> 10.0) 66 rake (~> 10.0)
29 - syspro! 67 + rubocop (~> 0.54.0)
  68 + syspro-ruby!
  69 + webmock (~> 3.3.0)
30 70
31 BUNDLED WITH 71 BUNDLED WITH
32 1.16.1 72 1.16.1
@@ -72,6 +72,103 @@ logged_off = Syspro::Logoff.logoff(guid) @@ -72,6 +72,103 @@ logged_off = Syspro::Logoff.logoff(guid)
72 ``` 72 ```
73 `logged_off` will be `true` if the user has been successfully logged off, and will contain an error string if an error has occured. 73 `logged_off` will be `true` if the user has been successfully logged off, and will contain an error string if an error has occured.
74 74
  75 +### Query
  76 +
  77 +#### Browse
  78 +Browse returns a paginated view of a particular table.
  79 +
  80 +This is an example using the generic Browse Business Object, `COMBRW`.
  81 +```rb
  82 +combrw = Syspro::BusinessObject::ComBrw.new
  83 +combrw.browse_name = "InvMaster"
  84 +combrw.start_condition = ""
  85 +combrw.return_rows = 5
  86 +combrw.filters = []
  87 +combrw.table_name = "InvMaster"
  88 +combrw.title = "StockCodes"
  89 +combrw.columns = [
  90 + {name: "StockCode"}
  91 +]
  92 +
  93 +browse_result = combrw.call(user_id.guid)
  94 +```
  95 +
  96 +`browse_result` will be a BrowseObject, which has the following structure:
  97 +
  98 +```rb
  99 +{
  100 + title: "Title",
  101 + rows: [ { name: "", value: "", data_type: "" } ],
  102 + next_prev_key: { name: "", text: "" },
  103 + header_details: { name: "", text: "" }
  104 +}
  105 +```
  106 +
  107 +#### Query
  108 +
  109 +Query gives control over joins between multiple tables over a single Business Object.
  110 +
  111 +This is an example using the generic Query Business Object, `COMFND`.
  112 +
  113 +```rb
  114 +comfnd = Syspro::BusinessObjects::ComFnd.new
  115 +comfnd.table_name = "InvMaster"
  116 +comfnd.return_rows = 5
  117 +comfnd.columns = [
  118 + {
  119 + name: "StockCode"
  120 + }
  121 +]
  122 +comfnd.expressions = [
  123 + {
  124 + andor: "And",
  125 + column: "StockCode",
  126 + condition: "EQ",
  127 + value: "02"
  128 + }
  129 +]
  130 +comfnd.order_by = "StockCode"
  131 +
  132 +query_result = comfnd.call(user_id.guid)
  133 +```
  134 +
  135 +This will return a QueryObject, which looks like this:
  136 +
  137 +```rb
  138 +{
  139 + header_details: { name: "", text: "" },
  140 + rows: [ { name: "", value: "" } ],
  141 + row_count: 1
  142 +}
  143 +```
  144 +
  145 +#### Fetch
  146 +
  147 +Fetch selects the `TOP 1` of the query.
  148 +
  149 +This is an example using the generic Fetch Business Object, `COMFCH`.
  150 +
  151 +```rb
  152 +comfch = Syspro::BusinessObjects::ComFch.new
  153 +comfch.table_name = "InvMaster"
  154 +comfch.key = "02"
  155 +comfch.optional_keys = []
  156 +comfch.full_key_provided = false
  157 +comfch.default_type = ""
  158 +comfch.espresso_fetch = true
  159 +
  160 +fetch_result = comfch.call(user_id.guid)
  161 +```
  162 +
  163 +This will return a FetchObject, with the following structure:
  164 +
  165 +```rb
  166 +{
  167 + table_name: "",
  168 + columns: [ { name: "", value: "" } ]
  169 +}
  170 +```
  171 +
75 ## Development 172 ## Development
76 173
77 After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake test` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment. 174 After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake test` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
@@ -80,6 +177,8 @@ To install this gem onto your local machine, run `bundle exec rake install`. To @@ -80,6 +177,8 @@ To install this gem onto your local machine, run `bundle exec rake install`. To
80 177
81 ## Contributing 178 ## Contributing
82 179
  180 +Run `bundle exec rake` and ensure everything looks good.
  181 +
83 Bug reports and pull requests are welcome on GitHub at https://github.com/ike/syspro-ruby. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [Contributor Covenant](http://contributor-covenant.org) code of conduct. 182 Bug reports and pull requests are welcome on GitHub at https://github.com/ike/syspro-ruby. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [Contributor Covenant](http://contributor-covenant.org) code of conduct.
84 183
85 ## License 184 ## License
@@ -88,4 +187,4 @@ The gem is available as open source under the terms of the [MIT License](https:/ @@ -88,4 +187,4 @@ The gem is available as open source under the terms of the [MIT License](https:/
88 187
89 ## Code of Conduct 188 ## Code of Conduct
90 189
91 -Everyone interacting in the Syspro project’s codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](https://github.com/ike/syspro-ruby/blob/master/CODE_OF_CONDUCT.md). 190 +Everyone interacting in the Syspro project’s codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](https://github.com/wildland/code-of-conduct).
1 -require "bundler/gem_tasks"  
2 -require "rake/testtask" 1 +# frozen_string_literal: true
  2 +
  3 +require 'bundler/gem_tasks'
  4 +require 'rake/testtask'
  5 +require 'rubocop/rake_task'
3 6
4 Rake::TestTask.new(:test) do |t| 7 Rake::TestTask.new(:test) do |t|
5 - t.libs << "test"  
6 - t.libs << "lib"  
7 - t.test_files = FileList["test/**/*_test.rb"] 8 + t.libs << 'test'
  9 + t.libs << 'lib'
  10 + t.test_files = FileList['test/**/*_test.rb']
8 end 11 end
9 12
10 -task :default => :test 13 +task default: %i[test rubocop]
  14 +
  15 +RuboCop::RakeTask.new(:rubocop) do |t|
  16 + t.options = ['--display-cop-names']
  17 +end
1 #!/usr/bin/env ruby 1 #!/usr/bin/env ruby
  2 +# frozen_string_literal: true
2 3
3 -require "bundler/setup"  
4 -require "syspro"  
5 -require "pry" 4 +require 'bundler/setup'
  5 +require 'syspro'
  6 +require 'pry'
6 7
7 Pry.start 8 Pry.start
1 -require "cgi"  
2 -require "faraday"  
3 -require "json"  
4 -require "logger"  
5 -require "openssl"  
6 -  
7 -require "syspro/api_resource"  
8 -require "syspro/errors"  
9 -require "syspro/get_logon_profile"  
10 -require "syspro/get_version"  
11 -require "syspro/logoff"  
12 -require "syspro/logon"  
13 -require "syspro/syspro_client"  
14 -require "syspro/singleton_api_resource"  
15 -require "syspro/syspro_object"  
16 -require "syspro/syspro_response"  
17 -require "syspro/util"  
18 -require "syspro/version"  
19 -  
20 -require "syspro/api_operations/request"  
21 - 1 +# frozen_string_literal: true
  2 +
  3 +require 'cgi'
  4 +require 'faraday'
  5 +require 'json'
  6 +require 'logger'
  7 +require 'openssl'
  8 +
  9 +require 'syspro/api_resource'
  10 +require 'syspro/errors'
  11 +require 'syspro/get_logon_profile'
  12 +require 'syspro/get_version'
  13 +require 'syspro/logoff'
  14 +require 'syspro/logon'
  15 +require 'syspro/syspro_client'
  16 +require 'syspro/singleton_api_resource'
  17 +require 'syspro/syspro_object'
  18 +require 'syspro/syspro_response'
  19 +require 'syspro/util'
  20 +require 'syspro/version'
  21 +
  22 +require 'syspro/api_operations/request'
  23 +require 'syspro/api_operations/query'
  24 +
  25 +require 'syspro/business_objects/combrw'
  26 +require 'syspro/business_objects/comfch'
  27 +require 'syspro/business_objects/comfnd'
  28 +
  29 +require 'syspro/business_objects/parsers/combrw_parser'
  30 +require 'syspro/business_objects/parsers/comfch_parser'
  31 +require 'syspro/business_objects/parsers/comfnd_parser'
  32 +
  33 +# Main Module
22 module Syspro 34 module Syspro
23 - @api_base = "http://syspro.wildlandlabs.com:90/SYSPROWCFService/Rest" 35 + @api_base = 'http://syspro.wildlandlabs.com:90/SYSPROWCFService/Rest'
24 36
25 @open_timeout = 30 37 @open_timeout = 30
26 @read_timeout = 80 38 @read_timeout = 80
@@ -60,14 +72,13 @@ module Syspro @@ -60,14 +72,13 @@ module Syspro
60 72
61 def self.log_level=(val) 73 def self.log_level=(val)
62 # Backwards compatibility for values that we briefly allowed 74 # Backwards compatibility for values that we briefly allowed
63 - if val == "debug"  
64 - val = LEVEL_DEBUG  
65 - elsif val == "info"  
66 - val = LEVEL_INFO  
67 - end  
68 - 75 + val = LEVEL_DEBUG if val == 'debug'
  76 + val = LEVEL_INFO if val == 'info'
69 if !val.nil? && ![LEVEL_DEBUG, LEVEL_ERROR, LEVEL_INFO].include?(val) 77 if !val.nil? && ![LEVEL_DEBUG, LEVEL_ERROR, LEVEL_INFO].include?(val)
70 - raise ArgumentError, "log_level should only be set to `nil`, `debug` or `info`" 78 + raise(
  79 + ArgumentError,
  80 + 'log_level should only be set to `nil`, `debug` or `info`'
  81 + )
71 end 82 end
72 @log_level = val 83 @log_level = val
73 end 84 end
@@ -95,5 +106,5 @@ module Syspro @@ -95,5 +106,5 @@ module Syspro
95 @max_network_retries = val.to_i 106 @max_network_retries = val.to_i
96 end 107 end
97 108
98 - Syspro.log_level = ENV["SYSPRO_LOG"] unless ENV["SYSPRO_LOG"].nil? 109 + Syspro.log_level = ENV['SYSPRO_LOG'] unless ENV['SYSPRO_LOG'].nil?
99 end 110 end
lib/syspro/api_operations/query.rb 0 → 100644
  1 +# frozen_string_literal: true
  2 +
  3 +module Syspro
  4 + module ApiOperations
  5 + module Query
  6 + module ClassMethods
  7 + def browse(params)
  8 + request(:get, '/Query/Browse', params)
  9 + end
  10 +
  11 + def fetch(params)
  12 + request(:get, '/Query/Fetch', params)
  13 + end
  14 +
  15 + def query(params)
  16 + request(:get, '/Query/Query', params)
  17 + end
  18 +
  19 + def find; end
  20 +
  21 + private
  22 +
  23 + def warn_on_opts_in_params(params)
  24 + Util::OPTS_USER_SPECIFIED.each do |opt|
  25 + if params.key?(opt)
  26 + warn("WARNING: #{opt} should be in opts instead of params.")
  27 + end
  28 + end
  29 + end
  30 + end # ClassMethods
  31 +
  32 + def self.included(base)
  33 + base.extend(ClassMethods)
  34 + end
  35 +
  36 + protected
  37 +
  38 + def request(method, url, params = {}, opts = {})
  39 + opts = @opts.merge(Util.normalize_opts(opts))
  40 + Request.request(method, url, params, opts)
  41 + end
  42 + end
  43 + end
  44 +end
lib/syspro/api_operations/request.rb
  1 +# frozen_string_literal: true
  2 +
1 module Syspro 3 module Syspro
2 module ApiOperations 4 module ApiOperations
3 module Request 5 module Request
@@ -28,7 +30,7 @@ module Syspro @@ -28,7 +30,7 @@ module Syspro
28 def warn_on_opts_in_params(params) 30 def warn_on_opts_in_params(params)
29 Util::OPTS_USER_SPECIFIED.each do |opt| 31 Util::OPTS_USER_SPECIFIED.each do |opt|
30 if params.key?(opt) 32 if params.key?(opt)
31 - $stderr.puts("WARNING: #{opt} should be in opts instead of params.") 33 + warn("WARNING: #{opt} should be in opts instead of params.")
32 end 34 end
33 end 35 end
34 end 36 end
@@ -37,14 +39,6 @@ module Syspro @@ -37,14 +39,6 @@ module Syspro
37 def self.included(base) 39 def self.included(base)
38 base.extend(ClassMethods) 40 base.extend(ClassMethods)
39 end 41 end
40 -  
41 - protected  
42 -  
43 - def request(method, url, params = {}, opts = {})  
44 - opts = @opts.merge(Util.normalize_opts(opts))  
45 - self.class.request(method, url, params, opts)  
46 - end  
47 end 42 end
48 end 43 end
49 end 44 end
50 -  
lib/syspro/api_resource.rb
1 -require "syspro/syspro_object"  
2 -require "syspro/api_operations/request" 1 +# frozen_string_literal: true
  2 +
  3 +require 'syspro/syspro_object'
  4 +require 'syspro/api_operations/request'
3 5
4 module Syspro 6 module Syspro
5 class ApiResource < SysproObject 7 class ApiResource < SysproObject
6 include Syspro::ApiOperations::Request 8 include Syspro::ApiOperations::Request
7 9
8 def self.class_name 10 def self.class_name
9 - name.split("::")[-1] 11 + name.split('::')[-1]
10 end 12 end
11 13
12 def self.resource_url 14 def self.resource_url
13 if self == ApiResource 15 if self == ApiResource
14 - raise NotImplementedError, "APIResource is an abstract class. You should perform actions on its subclasses (Charge, Customer, etc.)" 16 + raise NotImplementedError, 'APIResource is an abstract class. You should perform actions on its subclasses (Charge, Customer, etc.)'
15 end 17 end
16 "/#{CGI.escape(class_name.downcase)}" 18 "/#{CGI.escape(class_name.downcase)}"
17 end 19 end
lib/syspro/business_objects/combrw.rb 0 → 100644
  1 +# frozen_string_literal: true
  2 +
  3 +require 'syspro/business_objects/parsers/combrw_parser'
  4 +require 'erb'
  5 +
  6 +module Syspro
  7 + module BusinessObjects
  8 + class ComBrw < ApiResource
  9 + include Syspro::ApiOperations::Query
  10 + include Syspro::BusinessObjects::Parsers
  11 +
  12 + attr_accessor :browse_name, :start_condition, :return_rows, :filters,
  13 + :table_name, :title, :columns
  14 +
  15 + def call(user_id)
  16 + xml_in = template.result(binding)
  17 + params = { 'UserId' => user_id, 'XmlIn' => xml_in }
  18 + resp = ComBrw.browse(params)
  19 + parse_response(resp)
  20 + end
  21 +
  22 + def template
  23 + ERB.new(
  24 + File.read(
  25 + File.expand_path('schemas/combrw.xml.erb', File.dirname(__FILE__))
  26 + ),
  27 + nil,
  28 + '%'
  29 + )
  30 + end
  31 +
  32 + def parse_response(resp)
  33 + handle_errors(resp)
  34 + parser = ComBrwParser.new(resp[0].data)
  35 + parser.parse
  36 + end
  37 +
  38 + def handle_errors(resp)
  39 + body = resp[0].http_body
  40 + raise SysproError, body if body =~ /^(ERROR)/
  41 + end
  42 + end
  43 + end
  44 +end
lib/syspro/business_objects/comfch.rb 0 → 100644
  1 +# frozen_string_literal: true
  2 +
  3 +require 'syspro/business_objects/parsers/comfch_parser'
  4 +require 'erb'
  5 +
  6 +module Syspro
  7 + module BusinessObjects
  8 + class ComFch < ApiResource
  9 + include Syspro::ApiOperations::Query
  10 + include Syspro::BusinessObjects::Parsers
  11 +
  12 + attr_accessor :table_name, :key, :optional_keys, :full_key_provided,
  13 + :default_type, :espresso_fetch
  14 +
  15 + def call(user_id)
  16 + xml_in = template.result(binding)
  17 + params = { 'UserId' => user_id, 'XmlIn' => xml_in }
  18 + resp = ComFch.fetch(params)
  19 + parse_response(resp)
  20 + end
  21 +
  22 + def template
  23 + ERB.new(
  24 + File.read(
  25 + File.expand_path('schemas/comfch.xml.erb', File.dirname(__FILE__))
  26 + ),
  27 + nil,
  28 + '%'
  29 + )
  30 + end
  31 +
  32 + def parse_response(resp)
  33 + handle_errors(resp)
  34 + parser = ComFchParser.new(resp[0].data)
  35 + parser.parse
  36 + end
  37 +
  38 + def handle_errors(resp)
  39 + body = resp[0].http_body
  40 + raise SysproError, body if body =~ /^(ERROR)/
  41 + end
  42 + end
  43 + end
  44 +end
lib/syspro/business_objects/comfnd.rb 0 → 100644
  1 +# frozen_string_literal: true
  2 +
  3 +require 'syspro/business_objects/parsers/comfnd_parser'
  4 +require 'erb'
  5 +
  6 +module Syspro
  7 + module BusinessObjects
  8 + class ComFnd < ApiResource
  9 + include Syspro::ApiOperations::Query
  10 + include Syspro::BusinessObjects::Parsers
  11 +
  12 + attr_accessor :table_name, :return_rows, :columns, :expressions,
  13 + :order_by
  14 +
  15 + def call(user_id)
  16 + xml_in = template.result(binding)
  17 + business_object = 'COMFND'
  18 + params = { 'UserId' => user_id, 'BusinessObject' => business_object, 'XmlIn' => xml_in }
  19 + resp = ComFnd.query(params)
  20 + parse_response(resp)
  21 + end
  22 +
  23 + def template
  24 + ERB.new File.read(File.expand_path('schemas/comfnd.xml.erb', File.dirname(__FILE__))), nil, '%'
  25 + end
  26 +
  27 + def parse_response(resp)
  28 + handle_errors(resp)
  29 + parser = ComFndParser.new(resp[0].data)
  30 + parser.parse
  31 + end
  32 +
  33 + def handle_errors(resp)
  34 + body = resp[0].http_body
  35 + raise SysproError, body if body =~ /^(ERROR)/
  36 + end
  37 + end
  38 + end
  39 +end
lib/syspro/business_objects/parsers/combrw_parser.rb 0 → 100644
  1 +# frozen_string_literal: true
  2 +
  3 +module Syspro
  4 + module BusinessObjects
  5 + module Parsers
  6 + class ComBrwParser
  7 + attr_reader :doc
  8 +
  9 + def initialize(doc)
  10 + @doc = doc
  11 + end
  12 +
  13 + def parse
  14 + next_prev_key = doc.first_element_child.xpath('NextPrevKey')
  15 + next_prev_key_obj = next_prev_key.children.map do |el|
  16 + next if el.name == 'text'
  17 + {
  18 + name: el.name,
  19 + text: el.text
  20 + }
  21 + end.compact
  22 +
  23 + header_details = doc.first_element_child.xpath('HeaderDetails')
  24 + header_details_obj = header_details.children.map do |el|
  25 + next if el.name == 'text'
  26 + {
  27 + name: el.name,
  28 + text: el.text
  29 + }
  30 + end.compact
  31 +
  32 + rows = doc.first_element_child.xpath('Row')
  33 + rows_obj = rows.flat_map do |el|
  34 + el.elements.map do |inner|
  35 + {
  36 + name: inner.name,
  37 + value: inner.xpath('Value').text,
  38 + data_type: inner.xpath('DataType').text
  39 + }
  40 + end
  41 + end.compact
  42 +
  43 + BrowseObject.new(
  44 + doc.first_element_child.xpath('Title').text,
  45 + rows_obj,
  46 + next_prev_key_obj,
  47 + header_details_obj
  48 + )
  49 + end
  50 +
  51 + BrowseObject = Struct.new(:title, :rows, :next_prev_key, :header_details)
  52 + end
  53 + end
  54 + end
  55 +end
lib/syspro/business_objects/parsers/comfch_parser.rb 0 → 100644
  1 +# frozen_string_literal: true
  2 +
  3 +module Syspro
  4 + module BusinessObjects
  5 + module Parsers
  6 + class ComFchParser
  7 + attr_reader :doc
  8 +
  9 + def initialize(doc)
  10 + @doc = doc
  11 + end
  12 +
  13 + def parse
  14 + table_name = doc.first_element_child.name
  15 + columns = doc.first_element_child.elements
  16 + columns_obj = columns.map do |el|
  17 + { name: el.name, value: el.children.text }
  18 + end.compact
  19 +
  20 + FetchObject.new(
  21 + table_name,
  22 + columns_obj
  23 + )
  24 + end
  25 +
  26 + FetchObject = Struct.new(:table_name, :columns)
  27 + end
  28 + end
  29 + end
  30 +end
lib/syspro/business_objects/parsers/comfnd_parser.rb 0 → 100644
  1 +# frozen_string_literal: true
  2 +
  3 +module Syspro
  4 + module BusinessObjects
  5 + module Parsers
  6 + class ComFndParser
  7 + attr_reader :doc
  8 +
  9 + def initialize(doc)
  10 + @doc = doc
  11 + end
  12 +
  13 + def parse
  14 + header_details = doc.first_element_child.xpath('HeaderDetails')
  15 + header_details_obj = header_details.children.map do |el|
  16 + next if el.name == 'text'
  17 + {
  18 + name: el.name,
  19 + text: el.text
  20 + }
  21 + end.compact
  22 +
  23 + rows = doc.first_element_child.xpath('Row')
  24 + rows_obj = rows.flat_map do |el|
  25 + el.elements.map do |inner|
  26 + {
  27 + name: inner.name,
  28 + value: inner.children.text
  29 + }
  30 + end
  31 + end.compact
  32 +
  33 + QueryObject.new(
  34 + header_details_obj,
  35 + rows_obj,
  36 + doc.first_element_child.xpath('//RowsReturned').text.to_i
  37 + )
  38 + end
  39 +
  40 + QueryObject = Struct.new(:header_details, :rows, :row_count)
  41 + end
  42 + end
  43 + end
  44 +end
lib/syspro/business_objects/schemas/combrw.xml.erb 0 → 100644
  1 +<?xml version="1.0" encoding="Windows-1252"?>
  2 +<Browse xmlns:xsd="http://www.w3.org/2001/XMLSchema-instance" xsd:noNamespaceSchemaLocation="COMBRW.XSD">
  3 + <BrowseName><%= @browse_name %></BrowseName>
  4 + <StartAtKey/>
  5 + <StartCondition><%= @start_condition %></StartCondition>
  6 + <ReturnRows><%= @return_rows %></ReturnRows>
  7 + <% for @filter in @filters %>
  8 + <Filter>
  9 + <ColumnFilterName><%= @filter[:name] %></ColumnFilterName>
  10 + <ColumnFilterValue><%= @filter[:value] %></ColumnFilterValue>
  11 + </Filter>
  12 + <% end %>
  13 + <BrowseDetails>
  14 + <TableName><%= @table_name %></TableName>
  15 + <Title><%= @title %></Title>
  16 + <% for @column in @columns %>
  17 + <Column>
  18 + <% unless @column[:name].nil? %><ColumnName><%= @column[:name] %></ColumnName><% end %>
  19 + <% unless @column[:description].nil? %><ColumnDescription><%= @column[:description] %></ColumnDescription><% end %>
  20 + <% unless @column[:key].nil? %><ColumnKey><%= @column[:key] %></ColumnKey><% end %>
  21 + </Column>
  22 + <% end %>
  23 + </BrowseDetails>
  24 +</Browse>
  25 +
lib/syspro/business_objects/schemas/comfch.xml.erb 0 → 100644
  1 +<?xml version="1.0" encoding="Windows-1252"?>
  2 +<Fetch xmlns:xsd="http://www.w3.org/2001/XMLSchema-instance" xsd:noNamespaceSchemaLocation="COMFCH.XSD">
  3 + <TableName><%= @table_name %></TableName>
  4 + <Key><%= @key %></Key>
  5 + <% unless @optional_keys.empty? %>
  6 + <% @optional_keys.each_with_index do |key, i| %>
  7 + <<%= "OptionalKey#{ i + 1 }" %>><%= key[:value] %></<%= "OptionalKey#{ i + 1 }" %>>
  8 + <% end %>
  9 + <% end %>
  10 + <FullKeyProvided><%= @full_key_provided ? "N" : "Y" %></FullKeyProvided>
  11 + <DefaultType><%= @default_type %></DefaultType>
  12 + <EspressoFetch><%= @espresso_fetch ? "N" : "Y" %></EspressoFetch>
  13 +</Fetch>
  14 +
lib/syspro/business_objects/schemas/comfnd.xml.erb 0 → 100644
  1 +<?xml version="1.0" encoding="Windows-1252"?>
  2 +<Query xmlns:xsd="http://www.w3.org/2001/XMLSchema-instance" xsd:noNamespaceSchemaLocation="COMFND.XSD">
  3 + <TableName><%= @table_name %></TableName>
  4 + <ReturnRows><%= @return_rows %></ReturnRows>
  5 + <Columns>
  6 + <% for column in @columns %>
  7 + <Column><%= column[:name] %></Column>
  8 + <% end %>
  9 + </Columns>
  10 + <% unless @expressions.empty? %>
  11 + <Where>
  12 + <% for expression in @expressions %>
  13 + <Expression>
  14 + <OpenBracket>(</OpenBracket>
  15 + <% unless expression[:andor].nil? %>
  16 + <AndOr><%= expression[:andor] %></AndOr>
  17 + <% end %>
  18 + <Column><%= expression[:column] %></Column>
  19 + <Condition><%= expression[:condition] %></Condition>
  20 + <Value><%= expression[:value] %></Value>
  21 + <CloseBracket>)</CloseBracket>
  22 + </Expression>
  23 + <% end %>
  24 + </Where>
  25 + <% end %>
  26 + <OrderBy>
  27 + <Column><%= @order_by %></Column>
  28 + </OrderBy>
  29 +</Query>
lib/syspro/errors.rb
  1 +# frozen_string_literal: true
  2 +
1 module Syspro 3 module Syspro
2 class SysproError < StandardError 4 class SysproError < StandardError
3 attr_reader :message, :response, :code, :http_body, :http_headers, 5 attr_reader :message, :response, :code, :http_body, :http_headers,
@@ -16,8 +18,8 @@ module Syspro @@ -16,8 +18,8 @@ module Syspro
16 end 18 end
17 19
18 def to_s 20 def to_s
19 - status_string = @http_status.nil? ? "" : "(Status #{@http_status}) "  
20 - id_string = @request_id.nil? ? "" : "(Request #{@request_id}) " 21 + status_string = @http_status.nil? ? '' : "(Status #{@http_status}) "
  22 + id_string = @request_id.nil? ? '' : "(Request #{@request_id}) "
21 "#{status_string}#{id_string}#{@message}" 23 "#{status_string}#{id_string}#{@message}"
22 end 24 end
23 end 25 end
@@ -30,5 +32,4 @@ module Syspro @@ -30,5 +32,4 @@ module Syspro
30 32
31 class ApiError < SysproError 33 class ApiError < SysproError
32 end 34 end
33 -  
34 end 35 end
lib/syspro/get_logon_profile.rb
  1 +# frozen_string_literal: true
  2 +
1 module Syspro 3 module Syspro
2 class GetLogonProfile < ApiResource 4 class GetLogonProfile < ApiResource
3 def self.get_logon_profile(user_id) 5 def self.get_logon_profile(user_id)
4 - params = { "UserId" => user_id }  
5 - resp = self.request(:get, resource_url, params) 6 + params = { 'UserId' => user_id }
  7 + resp = request(:get, resource_url, params)
6 parse_response(resp[0]) 8 parse_response(resp[0])
7 end 9 end
8 10
9 def resource_url 11 def resource_url
10 - "/GetLogonProfile" 12 + '/GetLogonProfile'
11 end 13 end
12 14
13 - def self.parse_response(resp) 15 + def self.parse_response(resp) # rubocop:disable Metrics/MethodLength
14 doc = resp.data 16 doc = resp.data
15 17
16 - UserProfile.new(  
17 - doc.xpath("//CompanyName").text,  
18 - doc.xpath("//OperatorCode").text,  
19 - doc.xpath("//OperatorGroup").text,  
20 - doc.xpath("//OperatorEmailAddress").text,  
21 - doc.xpath("//OperatorLocation").text,  
22 - doc.xpath("//OperatorLanguageCode").text,  
23 - doc.xpath("//SystemLanguage").text,  
24 - doc.xpath("//AccountingDate").text,  
25 - doc.xpath("//CompanyDate").text,  
26 - doc.xpath("//DefaultArBranch").text,  
27 - doc.xpath("//DefaultApBranch").text,  
28 - doc.xpath("//DefaultBank").text,  
29 - doc.xpath("//DefaultWarehouse").text,  
30 - doc.xpath("//DefaultCustomer").text,  
31 - doc.xpath("//SystemSiteId").text,  
32 - doc.xpath("//SystemNationalityCode").text,  
33 - doc.xpath("//LocalCurrencyCode").text,  
34 - doc.xpath("//CurrencyDescription").text,  
35 - doc.xpath("//DefaultRequisitionUser").text,  
36 - doc.xpath("//XMLToHTMLTransform").text,  
37 - doc.xpath("//CssStyle").text,  
38 - doc.xpath("//CssSuffix").text,  
39 - doc.xpath("//DecimalFormat").text,  
40 - doc.xpath("//DateFormat").text,  
41 - doc.xpath("//FunctionalRole").text,  
42 - doc.xpath("//DatabaseType").text,  
43 - doc.xpath("//SysproVersion").text,  
44 - doc.xpath("//EnetVersion").text,  
45 - doc.xpath("//SysproServerBitWidth").text, 18 + OpenStruct.new(
  19 + company_name: doc.xpath('//CompanyName').text,
  20 + operator_code: doc.xpath('//OperatorCode').text,
  21 + operator_code: doc.xpath('//OperatorGroup').text,
  22 + operator_email_address: doc.xpath('//OperatorEmailAddress').text,
  23 + operator_location: doc.xpath('//OperatorLocation').text,
  24 + operator_language_code: doc.xpath('//OperatorLanguageCode').text,
  25 + system_language: doc.xpath('//SystemLanguage').text,
  26 + accounting_date: doc.xpath('//AccountingDate').text,
  27 + company_date: doc.xpath('//CompanyDate').text,
  28 + default_ar_branch: doc.xpath('//DefaultArBranch').text,
  29 + default_ap_branch: doc.xpath('//DefaultApBranch').text,
  30 + default_bank: doc.xpath('//DefaultBank').text,
  31 + default_warehouse: doc.xpath('//DefaultWarehouse').text,
  32 + default_customer: doc.xpath('//DefaultCustomer').text,
  33 + system_site_id: doc.xpath('//SystemSiteId').text,
  34 + system_nationality_code: doc.xpath('//SystemNationalityCode').text,
  35 + local_currency_code: doc.xpath('//LocalCurrencyCode').text,
  36 + currency_description: doc.xpath('//CurrencyDescription').text,
  37 + default_requisition_user: doc.xpath('//DefaultRequisitionUser').text,
  38 + xml_to_html_transform: doc.xpath('//XMLToHTMLTransform').text,
  39 + css_style: doc.xpath('//CssStyle').text,
  40 + css_suffix: doc.xpath('//CssSuffix').text,
  41 + decimal_format: doc.xpath('//DecimalFormat').text,
  42 + date_format: doc.xpath('//DateFormat').text,
  43 + functional_role: doc.xpath('//FunctionalRole').text,
  44 + database_type: doc.xpath('//DatabaseType').text,
  45 + syspro_version: doc.xpath('//SysproVersion').text,
  46 + enet_version: doc.xpath('//EnetVersion').text,
  47 + syspro_server_bit_width: doc.xpath('//SysproServerBitWidth').text
46 ) 48 )
47 end 49 end
48 private_class_method :parse_response 50 private_class_method :parse_response
49 -  
50 - UserProfile = Struct.new(:company_name, :operator_code, :operator_group, :operator_email_address,  
51 - :operator_location, :operator_language_code, :system_language, :accounting_date,  
52 - :company_date, :default_ar_branch, :default_ap_branch, :default_bank, :default_warehouse,  
53 - :default_customer, :system_site_id, :system_nationality_code, :local_currency_code,  
54 - :currency_description, :default_requisition_user, :xml_to_html_transform, :css_style,  
55 - :css_suffix, :decimal_format, :date_format, :functional_role, :database_type, :syspro_version,  
56 - :enet_version, :syspro_server_bit_width)  
57 end 51 end
58 end 52 end
59 -  
lib/syspro/get_version.rb
  1 +# frozen_string_literal: true
  2 +
1 module Syspro 3 module Syspro
2 class GetVersion < ApiResource 4 class GetVersion < ApiResource
3 def self.get_version 5 def self.get_version
4 - resp = self.request(:get, resource_url) 6 + resp = request(:get, resource_url)
5 VersionObject.new(resp[0].http_body) 7 VersionObject.new(resp[0].http_body)
6 end 8 end
7 9
8 def resource_url 10 def resource_url
9 - "/GetVersion" 11 + '/GetVersion'
10 end 12 end
11 13
12 VersionObject = Struct.new(:version) 14 VersionObject = Struct.new(:version)
13 end 15 end
14 end 16 end
15 -  
lib/syspro/logoff.rb
  1 +# frozen_string_literal: true
  2 +
1 module Syspro 3 module Syspro
2 class Logoff < ApiResource 4 class Logoff < ApiResource
3 def self.logoff(user_id) 5 def self.logoff(user_id)
4 - params = { "UserId" => user_id }  
5 - resp = self.request(:get, resource_url, params) 6 + params = { 'UserId' => user_id }
  7 + resp = request(:get, resource_url, params)
6 8
7 - if resp[0].http_body == "0" 9 + if resp[0].http_body == '0'
8 true 10 true
9 else 11 else
10 resp[0].http_body 12 resp[0].http_body
@@ -12,7 +14,7 @@ module Syspro @@ -12,7 +14,7 @@ module Syspro
12 end 14 end
13 15
14 def resource_url 16 def resource_url
15 - "/Logoff" 17 + '/Logoff'
16 end 18 end
17 end 19 end
18 end 20 end
lib/syspro/logon.rb
  1 +# frozen_string_literal: true
  2 +
1 module Syspro 3 module Syspro
2 class Logon < ApiResource 4 class Logon < ApiResource
3 def self.logon(username, password, company_id, company_password = nil) 5 def self.logon(username, password, company_id, company_password = nil)
4 params = { 6 params = {
5 - "Operator" => username,  
6 - "OperatorPassword" => password,  
7 - "CompanyId" => company_id,  
8 - "CompanyPassword" => company_password 7 + 'Operator' => username,
  8 + 'OperatorPassword' => password,
  9 + 'CompanyId' => company_id,
  10 + 'CompanyPassword' => company_password
9 } 11 }
10 - resp = self.request(:get, resource_url, params) 12 + resp = request(:get, resource_url, params)
11 UserIdObject.new(resp[0].http_body) 13 UserIdObject.new(resp[0].http_body)
12 end 14 end
13 15
14 def resource_url 16 def resource_url
15 - "/Logon" 17 + '/Logon'
16 end 18 end
17 19
18 UserIdObject = Struct.new(:guid) 20 UserIdObject = Struct.new(:guid)
19 end 21 end
20 end 22 end
21 -  
lib/syspro/singleton_api_resource.rb
1 -require_relative "api_resource" 1 +# frozen_string_literal: true
  2 +
  3 +require_relative 'api_resource'
2 4
3 module Syspro 5 module Syspro
4 class SingletonAPIResource < ApiResource 6 class SingletonAPIResource < ApiResource
5 def self.resource_url 7 def self.resource_url
6 if self == SingletonAPIResource 8 if self == SingletonAPIResource
7 - raise NotImplementedError, "SingletonAPIResource is an abstract class. You should perform actions on its subclasses (Customer, etc.)" 9 + raise(
  10 + NotImplementedError,
  11 + 'SingletonAPIResource is an abstract class. You should perform actions on its subclasses (Customer, etc.)' # rubocop:disable Metrics/LineLength
  12 + )
8 end 13 end
9 "/#{CGI.escape(class_name.downcase)}" 14 "/#{CGI.escape(class_name.downcase)}"
10 end 15 end
lib/syspro/syspro_client.rb
  1 +# frozen_string_literal: true
  2 +
1 module Syspro 3 module Syspro
2 - class SysproClient 4 + # This class is the main syspro client
  5 + class SysproClient # rubocop:disable Metrics/ClassLength
3 attr_accessor :conn, :api_base 6 attr_accessor :conn, :api_base
4 7
5 @verify_ssl_warned = false 8 @verify_ssl_warned = false
@@ -13,7 +16,7 @@ module Syspro @@ -13,7 +16,7 @@ module Syspro
13 Syspro::Logon.logon(username, password, company_id, company_password) 16 Syspro::Logon.logon(username, password, company_id, company_password)
14 end 17 end
15 18
16 - def get_syspro_version 19 + def get_syspro_version # rubocop:disable Naming/AccessorMethodName
17 Syspro::GetVersion.get_version 20 Syspro::GetVersion.get_version
18 end 21 end
19 22
@@ -22,13 +25,13 @@ module Syspro @@ -22,13 +25,13 @@ module Syspro
22 end 25 end
23 26
24 def self.default_client 27 def self.default_client
25 - Thread.current[:syspro_client_default_client] ||= SysproClient.new(default_conn) 28 + Thread.current[:syspro_client_default_client] ||= SysproClient.new(default_conn) # rubocop:disable Metrics/LineLength
26 end 29 end
27 30
28 # A default Faraday connection to be used when one isn't configured. This 31 # A default Faraday connection to be used when one isn't configured. This
29 # object should never be mutated, and instead instantiating your own 32 # object should never be mutated, and instead instantiating your own
30 # connection and wrapping it in a SysproClient object should be preferred. 33 # connection and wrapping it in a SysproClient object should be preferred.
31 - def self.default_conn 34 + def self.default_conn # rubocop:disable Metrics/MethodLength
32 # We're going to keep connections around so that we can take advantage 35 # We're going to keep connections around so that we can take advantage
33 # of connection re-use, so make sure that we have a separate connection 36 # of connection re-use, so make sure that we have a separate connection
34 # object per thread. 37 # object per thread.
@@ -40,20 +43,21 @@ module Syspro @@ -40,20 +43,21 @@ module Syspro
40 c.adapter Faraday.default_adapter 43 c.adapter Faraday.default_adapter
41 end 44 end
42 45
43 - # For now, we're not verifying SSL certificates. The warning will appear.  
44 - #if Syspro.verify_ssl_certs  
45 - #conn.ssl.verify = true  
46 - #conn.ssl.cert_store = Syspro.ca_store  
47 - #else 46 + # For now, we're not verifying SSL certificates.
  47 + # The warning will appear.
  48 + # if Syspro.verify_ssl_certs
  49 + # conn.ssl.verify = true
  50 + # conn.ssl.cert_store = Syspro.ca_store
  51 + # else
48 conn.ssl.verify = false 52 conn.ssl.verify = false
49 53
50 unless @verify_ssl_warned 54 unless @verify_ssl_warned
51 @verify_ssl_warned = true 55 @verify_ssl_warned = true
52 - $stderr.puts("WARNING: Running without SSL cert verification. " \  
53 - "You should never do this in production. " \ 56 + warn('WARNING: Running without SSL cert verification. ' \
  57 + 'You should never do this in production. ' \
54 "Execute 'Syspro.verify_ssl_certs = true' to enable verification.") 58 "Execute 'Syspro.verify_ssl_certs = true' to enable verification.")
55 end 59 end
56 - #end 60 + # end
57 61
58 conn 62 conn
59 end 63 end
@@ -77,9 +81,9 @@ module Syspro @@ -77,9 +81,9 @@ module Syspro
77 end 81 end
78 end 82 end
79 83
80 - def execute_request(method, path, user_id: nil, api_base: nil, headers: {}, params: {}) 84 + def execute_request(method, path, user_id: nil, api_base: nil, headers: {}, params: {}) # rubocop:disable Metrics/LineLength, Metrics/MethodLength
81 api_base ||= Syspro.api_base 85 api_base ||= Syspro.api_base
82 - user_id ||= "" 86 + user_id ||= ''
83 87
84 params = Util.objects_to_ids(params) 88 params = Util.objects_to_ids(params)
85 url = api_url(path, api_base) 89 url = api_url(path, api_base)
@@ -91,7 +95,7 @@ module Syspro @@ -91,7 +95,7 @@ module Syspro
91 when :get, :head, :delete 95 when :get, :head, :delete
92 query_params = params 96 query_params = params
93 else 97 else
94 - body = if headers[:content_type] && headers[:content_type] == "multipart/form-data" 98 + body = if headers[:content_type] && headers[:content_type] == 'multipart/form-data' # rubocop:disable Metrics/LineLength
95 params 99 params
96 else 100 else
97 Util.encode_parameters(params) 101 Util.encode_parameters(params)
@@ -108,7 +112,7 @@ module Syspro @@ -108,7 +112,7 @@ module Syspro
108 context.method = method 112 context.method = method
109 context.path = path 113 context.path = path
110 context.user_id = user_id 114 context.user_id = user_id
111 - context.query_params = query_params ? Util.encode_parameters(query_params) : nil 115 + context.query_params = query_params ? Util.encode_parameters(query_params) : nil # rubocop:disable Metrics/LineLength
112 116
113 http_resp = execute_request_with_rescues(api_base, context) do 117 http_resp = execute_request_with_rescues(api_base, context) do
114 conn.run_request(method, url, body, headers) do |req| 118 conn.run_request(method, url, body, headers) do |req|
@@ -135,22 +139,22 @@ module Syspro @@ -135,22 +139,22 @@ module Syspro
135 http_status: status, http_body: body) 139 http_status: status, http_body: body)
136 end 140 end
137 141
138 - def api_url(url = "", api_base = nil) 142 + def api_url(url = '', api_base = nil)
139 (api_base || Syspro.api_base) + url 143 (api_base || Syspro.api_base) + url
140 end 144 end
141 145
142 - def request_headers(method) 146 + def request_headers(_method)
143 user_agent = "Syspro/7 RubyBindings/#{Syspro::VERSION}" 147 user_agent = "Syspro/7 RubyBindings/#{Syspro::VERSION}"
144 148
145 headers = { 149 headers = {
146 - "User-Agent" => user_agent,  
147 - "Content-Type" => "application/x-www-form-urlencoded", 150 + 'User-Agent' => user_agent,
  151 + 'Content-Type' => 'application/x-www-form-urlencoded'
148 } 152 }
149 153
150 headers 154 headers
151 end 155 end
152 156
153 - def execute_request_with_rescues(api_base, context) 157 + def execute_request_with_rescues(api_base, context) # rubocop:disable Metrics/LineLength, Metrics/MethodLength
154 num_retries = 0 158 num_retries = 0
155 begin 159 begin
156 request_start = Time.now 160 request_start = Time.now
@@ -159,8 +163,8 @@ module Syspro @@ -159,8 +163,8 @@ module Syspro
159 log_response(context, request_start, resp.status, resp.body) 163 log_response(context, request_start, resp.status, resp.body)
160 164
161 # We rescue all exceptions from a request so that we have an easy spot to 165 # We rescue all exceptions from a request so that we have an easy spot to
162 - # implement our retry logic across the board. We'll re-raise if it's a type  
163 - # of exception that we didn't expect to handle. 166 + # implement our retry logic across the board. We'll re-raise if it's a
  167 + # type of exception that we didn't expect to handle.
164 rescue StandardError => e 168 rescue StandardError => e
165 if e.respond_to?(:response) && e.response 169 if e.respond_to?(:response) && e.response
166 log_response(context, request_start, 170 log_response(context, request_start,
@@ -178,9 +182,9 @@ module Syspro @@ -178,9 +182,9 @@ module Syspro
178 case e 182 case e
179 when Faraday::ClientError 183 when Faraday::ClientError
180 if e.response 184 if e.response
181 - handle_error_response(e.response, error_context) 185 + handle_error_response(e.response, context)
182 else 186 else
183 - handle_network_error(e, error_context, num_retries, api_base) 187 + handle_network_error(e, context, num_retries, api_base)
184 end 188 end
185 189
186 # Only handle errors when we know we can do so, and re-raise otherwise. 190 # Only handle errors when we know we can do so, and re-raise otherwise.
@@ -193,7 +197,36 @@ module Syspro @@ -193,7 +197,36 @@ module Syspro
193 resp 197 resp
194 end 198 end
195 199
196 - def self.should_retry?(e, num_retries) 200 + def handle_network_error(e, context, num_retries, api_base = nil) # rubocop:disable Metrics/LineLength, Metrics/MethodLength, Naming/UncommunicativeMethodParamName
  201 + Util.log_error('Syspro network error',
  202 + error_message: e.message,
  203 + request_id: context.request_id)
  204 +
  205 + case e
  206 + when Faraday::ConnectionFailed
  207 + message = 'Unexpected error communicating when trying to connect to Syspro.' # rubocop:disable Metrics/LineLength
  208 +
  209 + when Faraday::SSLError
  210 + message = 'Could not establish a secure connection to Syspro.'
  211 +
  212 + when Faraday::TimeoutError
  213 + api_base ||= Syspro.api_base
  214 + message = "Could not connect to Syspro (#{api_base}). " \
  215 + 'Please check your internet connection and try again. ' \
  216 + 'If this problem persists, you should check your Syspro service status.' # rubocop:disable Metrics/LineLength
  217 +
  218 + else
  219 + message = 'Unexpected error communicating with Syspro. ' \
  220 + 'If this problem persists, talk to your Syspro implementation team.'
  221 +
  222 + end
  223 +
  224 + message += " Request was retried #{num_retries} times." if num_retries.positive? # rubocop:disable Metrics/LineLength
  225 +
  226 + raise ApiConnectionError, message + "\n\n(Network error: #{e.message})"
  227 + end
  228 +
  229 + def self.should_retry?(e, num_retries) # rubocop:disable Metrics/LineLength, Naming/UncommunicativeMethodParamName
197 return false if num_retries >= Syspro.max_network_retries 230 return false if num_retries >= Syspro.max_network_retries
198 231
199 # Retry on timeout-related problems (either on open or read). 232 # Retry on timeout-related problems (either on open or read).
@@ -213,20 +246,20 @@ module Syspro @@ -213,20 +246,20 @@ module Syspro
213 end 246 end
214 247
215 def log_request(context, num_retries) 248 def log_request(context, num_retries)
216 - Util.log_info("Request to Syspro API", 249 + Util.log_info('Request to Syspro API',
217 account: context.account, 250 account: context.account,
218 api_version: context.api_version, 251 api_version: context.api_version,
219 method: context.method, 252 method: context.method,
220 num_retries: num_retries, 253 num_retries: num_retries,
221 path: context.path) 254 path: context.path)
222 - Util.log_debug("Request details", 255 + Util.log_debug('Request details',
223 body: context.body, 256 body: context.body,
224 query_params: context.query_params) 257 query_params: context.query_params)
225 end 258 end
226 private :log_request 259 private :log_request
227 260
228 - def log_response(context, request_start, status, body)  
229 - Util.log_info("Response from Syspro API", 261 + def log_response(context, request_start, status, body) # rubocop:disable Metrics/LineLength, Metrics/MethodLength
  262 + Util.log_info('Response from Syspro API',
230 account: context.account, 263 account: context.account,
231 api_version: context.api_version, 264 api_version: context.api_version,
232 elapsed: Time.now - request_start, 265 elapsed: Time.now - request_start,
@@ -234,14 +267,14 @@ module Syspro @@ -234,14 +267,14 @@ module Syspro
234 path: context.path, 267 path: context.path,
235 request_id: context.request_id, 268 request_id: context.request_id,
236 status: status) 269 status: status)
237 - Util.log_debug("Response details", 270 + Util.log_debug('Response details',
238 body: body, 271 body: body,
239 request_id: context.request_id) 272 request_id: context.request_id)
240 end 273 end
241 private :log_response 274 private :log_response
242 275
243 - def log_response_error(context, request_start, e)  
244 - Util.log_error("Request error", 276 + def log_response_error(context, request_start, e) # rubocop:disable Metrics/LineLength, Naming/UncommunicativeMethodParamName
  277 + Util.log_error('Request error',
245 elapsed: Time.now - request_start, 278 elapsed: Time.now - request_start,
246 error_message: e.message, 279 error_message: e.message,
247 method: context.method, 280 method: context.method,
@@ -249,6 +282,26 @@ module Syspro @@ -249,6 +282,26 @@ module Syspro
249 end 282 end
250 private :log_response_error 283 private :log_response_error
251 284
  285 + def handle_error_response(http_resp, context) # rubocop:disable Metrics/LineLength, Metrics/MethodLength
  286 + begin
  287 + resp = SysproResponse.from_faraday_hash(http_resp)
  288 + error_data = resp.data[:error]
  289 +
  290 + raise SysproError, 'Indeterminate error' unless error_data
  291 + rescue Nokogiri::XML::SyntaxError, SysproError
  292 + raise general_api_error(http_resp[:status], http_resp[:body])
  293 + end
  294 +
  295 + error = if error_data.is_a?(String)
  296 + specific_oauth_error(resp, error_data, context)
  297 + else
  298 + specific_api_error(resp, error_data, context)
  299 + end
  300 +
  301 + error.response = resp
  302 + raise(error)
  303 + end
  304 +
252 # RequestLogContext stores information about a request that's begin made so 305 # RequestLogContext stores information about a request that's begin made so
253 # that we can log certain information. It's useful because it means that we 306 # that we can log certain information. It's useful because it means that we
254 # don't have to pass around as many parameters. 307 # don't have to pass around as many parameters.
@@ -267,56 +320,55 @@ module Syspro @@ -267,56 +320,55 @@ module Syspro
267 # in so that we can generate a rich user agent header to help debug 320 # in so that we can generate a rich user agent header to help debug
268 # integrations. 321 # integrations.
269 class SystemProfiler 322 class SystemProfiler
270 - def self.uname  
271 - if File.exist?("/proc/version")  
272 - File.read("/proc/version").strip 323 + def self.uname # rubocop:disable Metrics/MethodLength
  324 + if File.exist?('/proc/version')
  325 + File.read('/proc/version').strip
273 else 326 else
274 - case RbConfig::CONFIG["host_os"] 327 + case RbConfig::CONFIG['host_os']
275 when /linux|darwin|bsd|sunos|solaris|cygwin/i 328 when /linux|darwin|bsd|sunos|solaris|cygwin/i
276 uname_from_system 329 uname_from_system
277 when /mswin|mingw/i 330 when /mswin|mingw/i
278 uname_from_system_ver 331 uname_from_system_ver
279 else 332 else
280 - "unknown platform" 333 + 'unknown platform'
281 end 334 end
282 end 335 end
283 end 336 end
284 337
285 def self.uname_from_system 338 def self.uname_from_system
286 - (`uname -a 2>/dev/null` || "").strip 339 + (`uname -a 2>/dev/null` || '').strip
287 rescue Errno::ENOENT 340 rescue Errno::ENOENT
288 - "uname executable not found" 341 + 'uname executable not found'
289 rescue Errno::ENOMEM # couldn't create subprocess 342 rescue Errno::ENOMEM # couldn't create subprocess
290 - "uname lookup failed" 343 + 'uname lookup failed'
291 end 344 end
292 345
293 def self.uname_from_system_ver 346 def self.uname_from_system_ver
294 - (`ver` || "").strip 347 + (`ver` || '').strip
295 rescue Errno::ENOENT 348 rescue Errno::ENOENT
296 - "ver executable not found" 349 + 'ver executable not found'
297 rescue Errno::ENOMEM # couldn't create subprocess 350 rescue Errno::ENOMEM # couldn't create subprocess
298 - "uname lookup failed" 351 + 'uname lookup failed'
299 end 352 end
300 353
301 def initialize 354 def initialize
302 @uname = self.class.uname 355 @uname = self.class.uname
303 end 356 end
304 357
305 - def user_agent  
306 - lang_version = "#{RUBY_VERSION} p#{RUBY_PATCHLEVEL} (#{RUBY_RELEASE_DATE})" 358 + def user_agent # rubocop:disable Metrics/MethodLength
  359 + lang_version = "#{RUBY_VERSION} p#{RUBY_PATCHLEVEL} (#{RUBY_RELEASE_DATE})" # rubocop:disable Metrics/LineLength
307 360
308 { 361 {
309 application: Syspro.app_info, 362 application: Syspro.app_info,
310 bindings_version: Syspro::VERSION, 363 bindings_version: Syspro::VERSION,
311 - lang: "ruby", 364 + lang: 'ruby',
312 lang_version: lang_version, 365 lang_version: lang_version,
313 platform: RUBY_PLATFORM, 366 platform: RUBY_PLATFORM,
314 - engine: defined?(RUBY_ENGINE) ? RUBY_ENGINE : "", 367 + engine: defined?(RUBY_ENGINE) ? RUBY_ENGINE : '',
315 uname: @uname, 368 uname: @uname,
316 - hostname: Socket.gethostname, 369 + hostname: Socket.gethostname
317 }.delete_if { |_k, v| v.nil? } 370 }.delete_if { |_k, v| v.nil? }
318 end 371 end
319 end 372 end
320 end 373 end
321 end 374 end
322 -  
lib/syspro/syspro_object.rb
  1 +# frozen_string_literal: true
  2 +
1 module Syspro 3 module Syspro
  4 + # This class represents a syspro response
2 class SysproObject 5 class SysproObject
3 include Enumerable 6 include Enumerable
4 7
@@ -18,7 +21,8 @@ module Syspro @@ -18,7 +21,8 @@ module Syspro
18 # considered to be equal if they have the same set of values and each one 21 # considered to be equal if they have the same set of values and each one
19 # of those values is the same. 22 # of those values is the same.
20 def ==(other) 23 def ==(other)
21 - other.is_a?(SysproObject) && @values == other.instance_variable_get(:@values) 24 + other.is_a?(SysproObject) &&
  25 + @values == other.instance_variable_get(:@values)
22 end 26 end
23 27
24 def to_s(*_args) 28 def to_s(*_args)
@@ -26,8 +30,8 @@ module Syspro @@ -26,8 +30,8 @@ module Syspro
26 end 30 end
27 31
28 def inspect 32 def inspect
29 - id_string = respond_to?(:id) && !id.nil? ? " id=#{id}" : ""  
30 - "#<#{self.class}:0x#{object_id.to_s(16)}#{id_string}> JSON: " + JSON.pretty_generate(@values) 33 + id_string = respond_to?(:id) && !id.nil? ? " id=#{id}" : ''
  34 + "#<#{self.class}:0x#{object_id.to_s(16)}#{id_string}> JSON: " + JSON.pretty_generate(@values) # rubocop:disable Metrics/LineLength
31 end 35 end
32 36
33 def keys 37 def keys
@@ -38,9 +42,9 @@ module Syspro @@ -38,9 +42,9 @@ module Syspro
38 @values.values 42 @values.values
39 end 43 end
40 44
41 - def to_hash 45 + def to_hash # rubocop:disable Metrics/MethodLength
42 maybe_to_hash = lambda do |value| 46 maybe_to_hash = lambda do |value|
43 - value && value.respond_to?(:to_hash) ? value.to_hash : value 47 + value&.respond_to?(:to_hash) ? value.to_hash : value
44 end 48 end
45 49
46 @values.each_with_object({}) do |(key, value), acc| 50 @values.each_with_object({}) do |(key, value), acc|
@@ -57,11 +61,9 @@ module Syspro @@ -57,11 +61,9 @@ module Syspro
57 @values.each(&blk) 61 @values.each(&blk)
58 end 62 end
59 63
60 - private  
61 -  
62 # Produces a deep copy of the given object including support for arrays, 64 # Produces a deep copy of the given object including support for arrays,
63 # hashes, and SysproObjects. 65 # hashes, and SysproObjects.
64 - def self.deep_copy(obj) 66 + def self.deep_copy(obj) # rubocop:disable Metrics/MethodLength
65 case obj 67 case obj
66 when Array 68 when Array
67 obj.map { |e| deep_copy(e) } 69 obj.map { |e| deep_copy(e) }
@@ -82,6 +84,5 @@ module Syspro @@ -82,6 +84,5 @@ module Syspro
82 end 84 end
83 end 85 end
84 private_class_method :deep_copy 86 private_class_method :deep_copy
85 -  
86 end 87 end
87 end 88 end
lib/syspro/syspro_response.rb
1 -require "nokogiri" 1 +# frozen_string_literal: true
  2 +
  3 +require 'nokogiri'
2 4
3 module Syspro 5 module Syspro
  6 + # This class represents a syspro response
4 class SysproResponse 7 class SysproResponse
5 attr_accessor :data, :http_body, :http_headers, :http_status, :request_id 8 attr_accessor :data, :http_body, :http_headers, :http_status, :request_id
6 9
@@ -12,7 +15,7 @@ module Syspro @@ -12,7 +15,7 @@ module Syspro
12 resp.data = Nokogiri::XML(resp.http_body) 15 resp.data = Nokogiri::XML(resp.http_body)
13 resp.http_headers = http_resp[:headers] 16 resp.http_headers = http_resp[:headers]
14 resp.http_status = http_resp[:status] 17 resp.http_status = http_resp[:status]
15 - resp.request_id = http_resp[:headers]["Request-Id"] 18 + resp.request_id = http_resp[:headers]['Request-Id']
16 resp 19 resp
17 end 20 end
18 21
@@ -23,7 +26,7 @@ module Syspro @@ -23,7 +26,7 @@ module Syspro
23 resp.data = Nokogiri::XML(resp.http_body) 26 resp.data = Nokogiri::XML(resp.http_body)
24 resp.http_headers = http_resp.headers 27 resp.http_headers = http_resp.headers
25 resp.http_status = http_resp.status 28 resp.http_status = http_resp.status
26 - resp.request_id = http_resp.headers["Request-Id"] 29 + resp.request_id = http_resp.headers['Request-Id']
27 resp 30 resp
28 end 31 end
29 end 32 end
lib/syspro/util.rb
  1 +# frozen_string_literal: true
  2 +
1 module Syspro 3 module Syspro
2 - class Util 4 + # Utillity class
  5 + class Util # rubocop:disable Metrics/ClassLength
3 # Options that a user is allowed to specify. 6 # Options that a user is allowed to specify.
4 OPTS_USER_SPECIFIED = Set[ 7 OPTS_USER_SPECIFIED = Set[
5 :user_id 8 :user_id
@@ -17,8 +20,7 @@ module Syspro @@ -17,8 +20,7 @@ module Syspro
17 OPTS_USER_SPECIFIED + Set[:client] 20 OPTS_USER_SPECIFIED + Set[:client]
18 ).freeze 21 ).freeze
19 22
20 -  
21 - def self.objects_to_ids(h) 23 + def self.objects_to_ids(h) # rubocop:disable Metrics/MethodLength, Metrics/LineLength, Naming/UncommunicativeMethodParamName
22 case h 24 case h
23 when ApiResource 25 when ApiResource
24 h.id 26 h.id
@@ -45,13 +47,17 @@ module Syspro @@ -45,13 +47,17 @@ module Syspro
45 # * +data+ - Hash of fields and values to be converted into a SysproObject. 47 # * +data+ - Hash of fields and values to be converted into a SysproObject.
46 # * +opts+ - Options for +SysproObject+ like an API key that will be reused 48 # * +opts+ - Options for +SysproObject+ like an API key that will be reused
47 # on subsequent API calls. 49 # on subsequent API calls.
48 - def self.convert_to_syspro_object(data, opts = {}) 50 + def self.convert_to_syspro_object(data, opts = {}) # rubocop:disable Metrics/LineLength, Metrics/MethodLength
49 case data 51 case data
50 when Array 52 when Array
51 data.map { |i| convert_to_syspro_object(i, opts) } 53 data.map { |i| convert_to_syspro_object(i, opts) }
52 when Hash 54 when Hash
53 - # Try converting to a known object class. If none available, fall back to generic SysproObject  
54 - object_classes.fetch(data[:object], SysproObject).construct_from(data, opts) 55 + # Try converting to a known object class.
  56 + # If none available, fall back to generic SysproObject
  57 + object_classes.fetch(
  58 + data[:object],
  59 + SysproObject
  60 + ).construct_from(data, opts)
55 else 61 else
56 data 62 data
57 end 63 end
@@ -66,7 +72,7 @@ module Syspro @@ -66,7 +72,7 @@ module Syspro
66 when Hash 72 when Hash
67 opts.clone 73 opts.clone
68 else 74 else
69 - raise TypeError, "normalize_opts expects a string or a hash" 75 + raise TypeError, 'normalize_opts expects a string or a hash'
70 end 76 end
71 end 77 end
72 78
@@ -78,7 +84,7 @@ module Syspro @@ -78,7 +84,7 @@ module Syspro
78 def self.normalize_headers(headers) 84 def self.normalize_headers(headers)
79 headers.each_with_object({}) do |(k, v), new_headers| 85 headers.each_with_object({}) do |(k, v), new_headers|
80 if k.is_a?(Symbol) 86 if k.is_a?(Symbol)
81 - k = titlecase_parts(k.to_s.tr("_", "-")) 87 + k = titlecase_parts(k.to_s.tr('_', '-'))
82 elsif k.is_a?(String) 88 elsif k.is_a?(String)
83 k = titlecase_parts(k) 89 k = titlecase_parts(k)
84 end 90 end
@@ -89,10 +95,10 @@ module Syspro @@ -89,10 +95,10 @@ module Syspro
89 95
90 def self.encode_parameters(params) 96 def self.encode_parameters(params)
91 Util.flatten_params(params) 97 Util.flatten_params(params)
92 - .map { |k, v| "#{url_encode(k)}=#{url_encode(v)}" }.join("&") 98 + .map { |k, v| "#{url_encode(k)}=#{url_encode(v)}" }.join('&')
93 end 99 end
94 100
95 - def self.flatten_params(params, parent_key = nil) 101 + def self.flatten_params(params, parent_key = nil) # rubocop:disable Metrics/LineLength, Metrics/MethodLength
96 result = [] 102 result = []
97 103
98 # do not sort the final output because arrays (and arrays of hashes 104 # do not sort the final output because arrays (and arrays of hashes
@@ -113,26 +119,41 @@ module Syspro @@ -113,26 +119,41 @@ module Syspro
113 end 119 end
114 120
115 def self.log_error(message, data = {}) 121 def self.log_error(message, data = {})
116 - if !Syspro.logger.nil? ||  
117 - !Syspro.log_level.nil? && Syspro.log_level <= Syspro::LEVEL_ERROR  
118 - log_internal(message, data, color: :cyan,  
119 - level: Syspro::LEVEL_ERROR, logger: Syspro.logger, out: $stderr) 122 + if !Syspro.logger.nil? || !Syspro.log_level.nil? && Syspro.log_level <= Syspro::LEVEL_ERROR # rubocop:disable Style/GuardClause, Metrics/LineLength
  123 + log_internal(
  124 + message,
  125 + data,
  126 + color: :cyan,
  127 + level: Syspro::LEVEL_ERROR,
  128 + logger: Syspro.logger,
  129 + out: $stderr
  130 + )
120 end 131 end
121 end 132 end
122 133
123 def self.log_info(message, data = {}) 134 def self.log_info(message, data = {})
124 - if !Syspro.logger.nil? ||  
125 - !Syspro.log_level.nil? && Syspro.log_level <= Syspro::LEVEL_INFO  
126 - log_internal(message, data, color: :cyan,  
127 - level: Syspro::LEVEL_INFO, logger: Syspro.logger, out: $stdout) 135 + if !Syspro.logger.nil? || !Syspro.log_level.nil? && Syspro.log_level <= Syspro::LEVEL_INFO # rubocop:disable Style/GuardClause, Metrics/LineLength
  136 + log_internal(
  137 + message,
  138 + data,
  139 + color: :cyan,
  140 + level: Syspro::LEVEL_INFO,
  141 + logger: Syspro.logger,
  142 + out: $stdout
  143 + )
128 end 144 end
129 end 145 end
130 146
131 def self.log_debug(message, data = {}) 147 def self.log_debug(message, data = {})
132 - if !Syspro.logger.nil? ||  
133 - !Syspro.log_level.nil? && Syspro.log_level <= Syspro::LEVEL_DEBUG  
134 - log_internal(message, data, color: :blue,  
135 - level: Syspro::LEVEL_DEBUG, logger: Syspro.logger, out: $stdout) 148 + if !Syspro.logger.nil? || !Syspro.log_level.nil? && Syspro.log_level <= Syspro::LEVEL_DEBUG # rubocop:disable Style/GuardClause, Metrics/LineLength
  149 + log_internal(
  150 + message,
  151 + data,
  152 + color: :blue,
  153 + level: Syspro::LEVEL_DEBUG,
  154 + logger: Syspro.logger,
  155 + out: $stdout
  156 + )
136 end 157 end
137 end 158 end
138 159
@@ -141,8 +162,47 @@ module Syspro @@ -141,8 +162,47 @@ module Syspro
141 # Don't use strict form encoding by changing the square bracket control 162 # Don't use strict form encoding by changing the square bracket control
142 # characters back to their literals. This is fine by the server, and 163 # characters back to their literals. This is fine by the server, and
143 # makes these parameter strings easier to read. 164 # makes these parameter strings easier to read.
144 - gsub("%5B", "[").gsub("%5D", "]") 165 + gsub('%5B', '[').gsub('%5D', ']')
145 end 166 end
  167 +
  168 + # TODO: Make these named required arguments when we drop support for Ruby
  169 + # 2.0.
  170 + def self.log_internal(message, data = {}, color: nil, level: nil, logger: nil, out: nil) # rubocop:disable Metrics/LineLength, Metrics/AbcSize, Metrics/MethodLength, Metrics/ParameterLists
  171 + data_str = data.reject { |_k, v| v.nil? }.map do |(k, v)|
  172 + format(
  173 + '%s=%s', # rubocop:disable Style/FormatStringToken
  174 + colorize(k, color, !out.nil? && out.isatty),
  175 + wrap_logfmt_value(v)
  176 + )
  177 + end.join(' ')
  178 +
  179 + if !logger.nil?
  180 + # the library's log levels are mapped to the same values as the
  181 + # standard library's logger
  182 + logger.log(
  183 + level,
  184 + format(
  185 + 'message=%s %s', # rubocop:disable Style/FormatStringToken
  186 + wrap_logfmt_value(message),
  187 + data_str
  188 + )
  189 + )
  190 + elsif out.isatty
  191 + out.puts format(
  192 + '%s %s %s', # rubocop:disable Style/FormatStringToken
  193 + colorize(level_name(level)[0, 4].upcase, color, out.isatty),
  194 + message,
  195 + data_str
  196 + )
  197 + else
  198 + out.puts format(
  199 + 'message=%s level=%s %s', # rubocop:disable Style/FormatStringToken
  200 + wrap_logfmt_value(message),
  201 + level_name(level),
  202 + data_str
  203 + )
  204 + end
  205 + end
  206 + private_class_method :log_internal
146 end 207 end
147 end 208 end
148 -  
lib/syspro/version.rb
  1 +# frozen_string_literal: true
  2 +
1 module Syspro 3 module Syspro
2 - VERSION = "0.1.0" 4 + VERSION = '1.0.0.alpha.1'
3 end 5 end
syspro-ruby.gemspec
1 -lib = File.expand_path("../lib", __FILE__) 1 +# frozen_string_literal: true
  2 +
  3 +lib = File.expand_path('lib', __dir__)
2 $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib) 4 $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
3 -require "syspro/version" 5 +require 'syspro/version'
4 6
5 Gem::Specification.new do |spec| 7 Gem::Specification.new do |spec|
6 - spec.name = "syspro-ruby" 8 + spec.name = 'syspro-ruby'
7 spec.version = Syspro::VERSION 9 spec.version = Syspro::VERSION
8 - spec.authors = ["Isaac Lewis"]  
9 - spec.email = ["isaac@ike.io"] 10 + spec.authors = ['Isaac Lewis']
  11 + spec.email = ['isaac@ike.io']
10 12
11 - spec.summary = %q{SYSPRO 7 Api Ruby adapter}  
12 - spec.license = "MIT" 13 + spec.summary = 'SYSPRO 7 Api Ruby adapter'
  14 + spec.license = 'MIT'
13 15
14 # Prevent pushing this gem to RubyGems.org. To allow pushes either set the 'allowed_push_host' 16 # Prevent pushing this gem to RubyGems.org. To allow pushes either set the 'allowed_push_host'
15 # to allow pushing to a single host or delete this section to allow pushing to any host. 17 # to allow pushing to a single host or delete this section to allow pushing to any host.
16 if spec.respond_to?(:metadata) 18 if spec.respond_to?(:metadata)
17 - spec.metadata["allowed_push_host"] = "http://rubygems.org" 19 + spec.metadata['allowed_push_host'] = 'http://rubygems.org'
18 else 20 else
19 - raise "RubyGems 2.0 or newer is required to protect against " \  
20 - "public gem pushes." 21 + raise 'RubyGems 2.0 or newer is required to protect against ' \
  22 + 'public gem pushes.'
21 end 23 end
  24 + spec.required_ruby_version = '>= 1.9.0'
22 25
23 - spec.files = `git ls-files -z`.split("\x0").reject do |f| 26 + spec.files = `git ls-files -z`.split("\x0").reject do |f|
24 f.match(%r{^(test|spec|features)/}) 27 f.match(%r{^(test|spec|features)/})
25 end 28 end
26 - spec.bindir = "exe" 29 + spec.bindir = 'exe'
27 spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) } 30 spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
28 - spec.require_paths = ["lib"] 31 + spec.require_paths = ['lib']
29 32
30 - spec.add_dependency("faraday", "~> 0.10") 33 + spec.add_dependency('faraday', '~> 0.10')
  34 + spec.add_dependency('nokogiri', '~> 1.8.2')
31 35
32 - spec.add_development_dependency "bundler", "~> 1.16"  
33 - spec.add_development_dependency "pry", "~> 0.11"  
34 - spec.add_development_dependency "rake", "~> 10.0"  
35 - spec.add_development_dependency "minitest", "~> 5.0" 36 + spec.add_development_dependency 'bundler', '~> 1.16'
  37 + spec.add_development_dependency 'minitest', '~> 5.0'
  38 + spec.add_development_dependency 'pry', '~> 0.11'
  39 + spec.add_development_dependency 'rake', '~> 10.0'
  40 + spec.add_development_dependency 'rubocop', '~> 0.54.0'
  41 + spec.add_development_dependency 'webmock', '~> 3.3.0'
  42 + spec.add_development_dependency 'minitest-vcr', '~> 1.4.0'
36 end 43 end
test/cassettes/test_client_block_execution.yml 0 → 100644
  1 +---
  2 +http_interactions:
  3 +- request:
  4 + method: get
  5 + uri: http://syspro.wildlandlabs.com:90/SYSPROWCFService/Rest/getversion
  6 + body:
  7 + encoding: US-ASCII
  8 + string: ''
  9 + headers:
  10 + User-Agent:
  11 + - Syspro/7 RubyBindings/0.1.0
  12 + Content-Type:
  13 + - application/x-www-form-urlencoded
  14 + Accept-Encoding:
  15 + - gzip;q=1.0,deflate;q=0.6,identity;q=0.3
  16 + Accept:
  17 + - "*/*"
  18 + response:
  19 + status:
  20 + code: 200
  21 + message: OK
  22 + headers:
  23 + Content-Length:
  24 + - '7'
  25 + Content-Type:
  26 + - application/octet-stream
  27 + Server:
  28 + - Microsoft-HTTPAPI/2.0
  29 + Date:
  30 + - Fri, 06 Apr 2018 19:23:43 GMT
  31 + body:
  32 + encoding: UTF-8
  33 + string: 7.0.0.6
  34 + http_version:
  35 + recorded_at: Fri, 06 Apr 2018 19:23:42 GMT
  36 +recorded_with: VCR 4.0.0
test/cassettes/test_get_syspro_version.yml 0 → 100644
  1 +---
  2 +http_interactions:
  3 +- request:
  4 + method: get
  5 + uri: http://syspro.wildlandlabs.com:90/SYSPROWCFService/Rest/getversion
  6 + body:
  7 + encoding: US-ASCII
  8 + string: ''
  9 + headers:
  10 + User-Agent:
  11 + - Syspro/7 RubyBindings/0.1.0
  12 + Content-Type:
  13 + - application/x-www-form-urlencoded
  14 + Accept-Encoding:
  15 + - gzip;q=1.0,deflate;q=0.6,identity;q=0.3
  16 + Accept:
  17 + - "*/*"
  18 + response:
  19 + status:
  20 + code: 200
  21 + message: OK
  22 + headers:
  23 + Content-Length:
  24 + - '7'
  25 + Content-Type:
  26 + - application/octet-stream
  27 + Server:
  28 + - Microsoft-HTTPAPI/2.0
  29 + Date:
  30 + - Fri, 06 Apr 2018 19:23:43 GMT
  31 + body:
  32 + encoding: UTF-8
  33 + string: 7.0.0.6
  34 + http_version:
  35 + recorded_at: Fri, 06 Apr 2018 19:23:42 GMT
  36 +recorded_with: VCR 4.0.0
test/cassettes/test_logoff_error.yml 0 → 100644
  1 +---
  2 +http_interactions:
  3 +- request:
  4 + method: get
  5 + uri: http://syspro.wildlandlabs.com:90/SYSPROWCFService/Rest/logoff?UserId=1BB5B3050954BB459A5D034DB5CC386980
  6 + body:
  7 + encoding: US-ASCII
  8 + string: ''
  9 + headers:
  10 + User-Agent:
  11 + - Syspro/7 RubyBindings/0.1.0
  12 + Content-Type:
  13 + - application/x-www-form-urlencoded
  14 + Accept-Encoding:
  15 + - gzip;q=1.0,deflate;q=0.6,identity;q=0.3
  16 + Accept:
  17 + - "*/*"
  18 + response:
  19 + status:
  20 + code: 200
  21 + message: OK
  22 + headers:
  23 + Content-Length:
  24 + - '72'
  25 + Content-Type:
  26 + - application/octet-stream
  27 + Server:
  28 + - Microsoft-HTTPAPI/2.0
  29 + Date:
  30 + - Fri, 06 Apr 2018 19:22:53 GMT
  31 + body:
  32 + encoding: UTF-8
  33 + string: 'ERROR: Unable to read the SYSPRO base directory registry string BaseDir8'
  34 + http_version:
  35 + recorded_at: Fri, 06 Apr 2018 19:22:52 GMT
  36 +recorded_with: VCR 4.0.0
test/cassettes/test_logon.yml 0 → 100644
  1 +---
  2 +http_interactions:
  3 +- request:
  4 + method: get
  5 + uri: http://syspro.wildlandlabs.com:90/SYSPROWCFService/Rest/logon?CompanyId=L&CompanyPassword=&Operator=wland&OperatorPassword=piperita2016
  6 + body:
  7 + encoding: US-ASCII
  8 + string: ''
  9 + headers:
  10 + User-Agent:
  11 + - Syspro/7 RubyBindings/0.1.0
  12 + Content-Type:
  13 + - application/x-www-form-urlencoded
  14 + Accept-Encoding:
  15 + - gzip;q=1.0,deflate;q=0.6,identity;q=0.3
  16 + Accept:
  17 + - "*/*"
  18 + response:
  19 + status:
  20 + code: 200
  21 + message: OK
  22 + headers:
  23 + Content-Length:
  24 + - '36'
  25 + Content-Type:
  26 + - application/octet-stream
  27 + Server:
  28 + - Microsoft-HTTPAPI/2.0
  29 + Date:
  30 + - Fri, 06 Apr 2018 19:23:52 GMT
  31 + body:
  32 + encoding: UTF-8
  33 + string: 'EC3098D6E284FB44ADF10671B3F06FCA00 '
  34 + http_version:
  35 + recorded_at: Fri, 06 Apr 2018 19:23:51 GMT
  36 +recorded_with: VCR 4.0.0
test/cassettes/test_query_browse.yml 0 → 100644
  1 +---
  2 +http_interactions:
  3 +- request:
  4 + method: get
  5 + uri: http://syspro.wildlandlabs.com:90/SYSPROWCFService/Rest/logon?CompanyId=L&CompanyPassword=&Operator=wland&OperatorPassword=piperita2016
  6 + body:
  7 + encoding: US-ASCII
  8 + string: ''
  9 + headers:
  10 + User-Agent:
  11 + - Syspro/7 RubyBindings/0.1.0
  12 + Content-Type:
  13 + - application/x-www-form-urlencoded
  14 + Accept-Encoding:
  15 + - gzip;q=1.0,deflate;q=0.6,identity;q=0.3
  16 + Accept:
  17 + - "*/*"
  18 + response:
  19 + status:
  20 + code: 200
  21 + message: OK
  22 + headers:
  23 + Content-Length:
  24 + - '36'
  25 + Content-Type:
  26 + - application/octet-stream
  27 + Server:
  28 + - Microsoft-HTTPAPI/2.0
  29 + Date:
  30 + - Fri, 06 Apr 2018 19:23:03 GMT
  31 + body:
  32 + encoding: UTF-8
  33 + string: '64C41E1DEB73024CA660211DE643705700 '
  34 + http_version:
  35 + recorded_at: Fri, 06 Apr 2018 19:23:01 GMT
  36 +- request:
  37 + method: get
  38 + uri: http://syspro.wildlandlabs.com:90/SYSPROWCFService/Rest/Query/Browse?UserId=64C41E1DEB73024CA660211DE643705700%20%20&XmlIn=%3C?xml%20version=%221.0%22%20encoding=%22Windows-1252%22?%3E%0A%3CBrowse%20xmlns:xsd=%22http://www.w3.org/2001/XMLSchema-instance%22%20xsd:noNamespaceSchemaLocation=%22COMBRW.XSD%22%3E%0A%20%20%3CBrowseName%3EInvMaster%3C/BrowseName%3E%0A%20%20%3CStartAtKey/%3E%0A%20%20%3CStartCondition%3E%3C/StartCondition%3E%0A%20%20%3CReturnRows%3E5%3C/ReturnRows%3E%0A%20%20%0A%20%20%3CBrowseDetails%3E%0A%20%20%20%20%3CTableName%3EInvMaster%3C/TableName%3E%0A%20%20%20%20%3CTitle%3EStockCodes%3C/Title%3E%0A%20%20%20%20%0A%20%20%20%20%20%20%3CColumn%3E%0A%20%20%20%20%20%20%20%20%3CColumnName%3EStockCode%3C/ColumnName%3E%0A%20%20%20%20%20%20%20%20%0A%20%20%20%20%20%20%20%20%0A%20%20%20%20%20%20%3C/Column%3E%0A%20%20%20%20%0A%20%20%3C/BrowseDetails%3E%0A%3C/Browse%3E%0A%0A
  39 + body:
  40 + encoding: US-ASCII
  41 + string: ''
  42 + headers:
  43 + User-Agent:
  44 + - Syspro/7 RubyBindings/0.1.0
  45 + Content-Type:
  46 + - application/x-www-form-urlencoded
  47 + Accept-Encoding:
  48 + - gzip;q=1.0,deflate;q=0.6,identity;q=0.3
  49 + Accept:
  50 + - "*/*"
  51 + response:
  52 + status:
  53 + code: 200
  54 + message: OK
  55 + headers:
  56 + Content-Length:
  57 + - '944'
  58 + Content-Type:
  59 + - application/octet-stream
  60 + Server:
  61 + - Microsoft-HTTPAPI/2.0
  62 + Date:
  63 + - Fri, 06 Apr 2018 19:23:07 GMT
  64 + body:
  65 + encoding: UTF-8
  66 + string: "<?xml version=\"1.0\" encoding=\"Windows-1252\"?>\n<InvMaster Language='05'
  67 + Language2='EN' CssStyle='' DecFormat='1' DateFormat='01' Role='01' Version='7.0.036'
  68 + OperatorPrimaryRole=' '>\n<Title>StockCodes</Title>\n<Row>\n<StockCode>\n<Value>02</Value>\n<DataType>AlphaNumeric</DataType>\n</StockCode>\n</Row>\n<Row>\n<StockCode>\n<Value>021</Value>\n<DataType>AlphaNumeric</DataType>\n</StockCode>\n</Row>\n<Row>\n<StockCode>\n<Value>0214011IFF</Value>\n<DataType>AlphaNumeric</DataType>\n</StockCode>\n</Row>\n<Row>\n<StockCode>\n<Value>022</Value>\n<DataType>AlphaNumeric</DataType>\n</StockCode>\n</Row>\n<Row>\n<StockCode>\n<Value>023</Value>\n<DataType>AlphaNumeric</DataType>\n</StockCode>\n</Row>\n<NextPrevKey>\n<PrevKey>02</PrevKey>\n<NextKey>023</NextKey>\n<Fwd>True</Fwd>\n<Back>False</Back>\n</NextPrevKey>\n<HeaderDetails>\n<Header>Stock
  69 + code</Header>\n<Key>StockCode</Key>\n<KeyDescription>Stock code</KeyDescription>\n<Table>InvMaster</Table>\n</HeaderDetails>\n</InvMaster>\n "
  70 + http_version:
  71 + recorded_at: Fri, 06 Apr 2018 19:23:06 GMT
  72 +recorded_with: VCR 4.0.0
test/cassettes/test_query_fetch.yml 0 → 100644
  1 +---
  2 +http_interactions:
  3 +- request:
  4 + method: get
  5 + uri: http://syspro.wildlandlabs.com:90/SYSPROWCFService/Rest/logon?CompanyId=L&CompanyPassword=&Operator=wland&OperatorPassword=piperita2016
  6 + body:
  7 + encoding: US-ASCII
  8 + string: ''
  9 + headers:
  10 + User-Agent:
  11 + - Syspro/7 RubyBindings/0.1.0
  12 + Content-Type:
  13 + - application/x-www-form-urlencoded
  14 + Accept-Encoding:
  15 + - gzip;q=1.0,deflate;q=0.6,identity;q=0.3
  16 + Accept:
  17 + - "*/*"
  18 + response:
  19 + status:
  20 + code: 200
  21 + message: OK
  22 + headers:
  23 + Content-Length:
  24 + - '36'
  25 + Content-Type:
  26 + - application/octet-stream
  27 + Server:
  28 + - Microsoft-HTTPAPI/2.0
  29 + Date:
  30 + - Fri, 06 Apr 2018 19:23:16 GMT
  31 + body:
  32 + encoding: UTF-8
  33 + string: '5B21680F424424498B7CE7CCDA98B41700 '
  34 + http_version:
  35 + recorded_at: Fri, 06 Apr 2018 19:23:15 GMT
  36 +- request:
  37 + method: get
  38 + uri: http://syspro.wildlandlabs.com:90/SYSPROWCFService/Rest/Query/Fetch?UserId=5B21680F424424498B7CE7CCDA98B41700%20%20&XmlIn=%3C?xml%20version=%221.0%22%20encoding=%22Windows-1252%22?%3E%0A%3CFetch%20xmlns:xsd=%22http://www.w3.org/2001/XMLSchema-instance%22%20xsd:noNamespaceSchemaLocation=%22COMFCH.XSD%22%3E%0A%20%20%3CTableName%3EInvMaster%3C/TableName%3E%0A%20%20%3CKey%3E02%3C/Key%3E%0A%20%20%0A%20%20%3CFullKeyProvided%3EY%3C/FullKeyProvided%3E%0A%20%20%3CDefaultType%3E%3C/DefaultType%3E%0A%20%20%3CEspressoFetch%3EN%3C/EspressoFetch%3E%0A%3C/Fetch%3E%0A%0A
  39 + body:
  40 + encoding: US-ASCII
  41 + string: ''
  42 + headers:
  43 + User-Agent:
  44 + - Syspro/7 RubyBindings/0.1.0
  45 + Content-Type:
  46 + - application/x-www-form-urlencoded
  47 + Accept-Encoding:
  48 + - gzip;q=1.0,deflate;q=0.6,identity;q=0.3
  49 + Accept:
  50 + - "*/*"
  51 + response:
  52 + status:
  53 + code: 200
  54 + message: OK
  55 + headers:
  56 + Content-Length:
  57 + - '3911'
  58 + Content-Type:
  59 + - application/octet-stream
  60 + Server:
  61 + - Microsoft-HTTPAPI/2.0
  62 + Date:
  63 + - Fri, 06 Apr 2018 19:23:26 GMT
  64 + body:
  65 + encoding: UTF-8
  66 + string: "<?xml version=\"1.0\" encoding=\"Windows-1252\"?>\n<InvMaster Language='05'
  67 + Language2='EN' CssStyle='' DecFormat='1' DateFormat='01' Role='01' Version='7.0.035'
  68 + OperatorPrimaryRole=' '>\n<StockCode>02</StockCode>\n<Description>STR</Description>\n<LongDesc>SPEARMINT
  69 + TERPENES</LongDesc>\n<AlternateKey1/>\n<AlternateKey2/>\n<EccUser/>\n<StockUom>LB</StockUom>\n<AlternateUom>KG</AlternateUom>\n<OtherUom>DR</OtherUom>\n<ConvFactAltUom>
  70 + \ 2.204620</ConvFactAltUom>\n<ConvMulDiv>M</ConvMulDiv>\n<ConvFactOthUom>
  71 + \ 400.000000</ConvFactOthUom>\n<MulDiv>M</MulDiv>\n<Mass> 1.000000</Mass>\n<Volume>
  72 + \ 0.000000</Volume>\n<Decimals>3</Decimals>\n<PriceCategory>A</PriceCategory>\n<PriceMethod>C</PriceMethod>\n<Supplier/>\n<CycleCount>
  73 + 0</CycleCount>\n<ProductClass>STRP</ProductClass>\n<TaxCode>A</TaxCode>\n<OtherTaxCode/>\n<ListPriceCode>A</ListPriceCode>\n<SerialMethod>N</SerialMethod>\n<InterfaceFlag>Y</InterfaceFlag>\n<KitType>N</KitType>\n<LowLevelCode>
  74 + 0</LowLevelCode>\n<Buyer/>\n<Planner/>\n<TraceableType>T</TraceableType>\n<MpsFlag>N</MpsFlag>\n<BulkIssueFlag>N</BulkIssueFlag>\n<AbcClass/>\n<LeadTime>
  75 + \ 0</LeadTime>\n<StockMovementReq>Y</StockMovementReq>\n<ClearingFlag>N</ClearingFlag>\n<SupercessionDate>0000-00-00</SupercessionDate>\n<AbcAnalysisReq>Y</AbcAnalysisReq>\n<AbcCostingReq>N</AbcCostingReq>\n<CostUom>LB</CostUom>\n<MinPricePct>
  76 + \ 0.00</MinPricePct>\n<LabourCost> 0.00000</LabourCost>\n<MaterialCost>
  77 + \ 0.00000</MaterialCost>\n<FixOverhead> 0.00000</FixOverhead>\n<VariableOverhead>
  78 + \ 0.00000</VariableOverhead>\n<PartCategory>B</PartCategory>\n<DrawOfficeNum/>\n<WarehouseToUse>H1</WarehouseToUse>\n<BuyingRule>A</BuyingRule>\n<SpecificGravity>
  79 + 0.0000</SpecificGravity>\n<ImplosionNum> 0</ImplosionNum>\n<Ebq> 400.000000</Ebq>\n<ComponentCount>
  80 + \ 0</ComponentCount>\n<FixTimePeriod> 1</FixTimePeriod>\n<PanSize> 0.000000</PanSize>\n<DockToStock>
  81 + \ 0</DockToStock>\n<OutputMassFlag>F</OutputMassFlag>\n<ShelfLife>
  82 + \ 0</ShelfLife>\n<Version/>\n<Release/>\n<DemandTimeFence> 0</DemandTimeFence>\n<MakeToOrderFlag>N</MakeToOrderFlag>\n<ManufLeadTime>
  83 + \ 0</ManufLeadTime>\n<GrossReqRule>I</GrossReqRule>\n<PercentageYield>100</PercentageYield>\n<AbcPreProd>
  84 + \ 0.00000</AbcPreProd>\n<AbcManufacturing> 0.00000</AbcManufacturing>\n<AbcSales>
  85 + \ 0.00000</AbcSales>\n<AbcCumPreProd> 0.00000</AbcCumPreProd>\n<AbcCumManuf>
  86 + \ 0.00000</AbcCumManuf>\n<WipCtlGlCode/>\n<ResourceCode/>\n<GstTaxCode>A</GstTaxCode>\n<PrcInclGst>N</PrcInclGst>\n<SerEntryAtSale/>\n<StpSelection/>\n<UserField1/>\n<UserField2>
  87 + \ 0.00000</UserField2>\n<UserField3/>\n<UserField4/>\n<UserField5/>\n<TariffCode/>\n<SupplementaryUnit>N</SupplementaryUnit>\n<EbqPan>E</EbqPan>\n<StdLandedCost>
  88 + \ 0.00000</StdLandedCost>\n<LctRequired>N</LctRequired>\n<StdLctRoute/>\n<IssMultLotsFlag>Y</IssMultLotsFlag>\n<InclInStrValid>Y</InclInStrValid>\n<StdLabCostsBill>
  89 + \ 0.00000</StdLabCostsBill>\n<PhantomIfComp/>\n<CountryOfOrigin/>\n<StockOnHold/>\n<StockOnHoldReason/>\n<EccFlag>N</EccFlag>\n<StockAndAltUm>N</StockAndAltUm>\n<AltUnitChar>0</AltUnitChar>\n<JobsOnHold/>\n<JobHoldAllocs/>\n<PurchOnHold/>\n<SalesOnHold/>\n<MaintOnHold/>\n<BatchBill>N</BatchBill>\n<BlanketPoExists/>\n<CallOffBpoExists/>\n<DistWarehouseToUse/>\n<JobClassification/>\n<SubContractCost>
  90 + \ 0.00000</SubContractCost>\n<DateStkAdded>2005-08-26</DateStkAdded>\n<InspectionFlag/>\n<SerialPrefix/>\n<SerialSuffix/>\n<ReturnableItem/>\n<ProductGroup/>\n<PriceType/>\n<Basis/>\n<ManualCostFlag/>\n<ManufactureUom>LB</ManufactureUom>\n<ConvFactMuM>
  91 + \ 1.000000</ConvFactMuM>\n<ManMulDiv>M</ManMulDiv>\n<LookAheadWin> 0</LookAheadWin>\n<LoadingFactor>
  92 + \ 0.000</LoadingFactor>\n<SupplUnitCode/>\n<StorageSecurity/>\n<StorageHazard/>\n<StorageCondition/>\n<ProductShelfLife>
  93 + \ 0</ProductShelfLife>\n<InternalShelfLife> 0</InternalShelfLife>\n<AltMethodFlag/>\n<AltSisoFlag/>\n<AltReductionFlag/>\n<WithTaxExpenseType/>\n</InvMaster>\n "
  94 + http_version:
  95 + recorded_at: Fri, 06 Apr 2018 19:23:24 GMT
  96 +recorded_with: VCR 4.0.0
test/cassettes/test_query_query.yml 0 → 100644
  1 +---
  2 +http_interactions:
  3 +- request:
  4 + method: get
  5 + uri: http://syspro.wildlandlabs.com:90/SYSPROWCFService/Rest/logon?CompanyId=L&CompanyPassword=&Operator=wland&OperatorPassword=piperita2016
  6 + body:
  7 + encoding: US-ASCII
  8 + string: ''
  9 + headers:
  10 + User-Agent:
  11 + - Syspro/7 RubyBindings/0.1.0
  12 + Content-Type:
  13 + - application/x-www-form-urlencoded
  14 + Accept-Encoding:
  15 + - gzip;q=1.0,deflate;q=0.6,identity;q=0.3
  16 + Accept:
  17 + - "*/*"
  18 + response:
  19 + status:
  20 + code: 200
  21 + message: OK
  22 + headers:
  23 + Content-Length:
  24 + - '36'
  25 + Content-Type:
  26 + - application/octet-stream
  27 + Server:
  28 + - Microsoft-HTTPAPI/2.0
  29 + Date:
  30 + - Fri, 06 Apr 2018 19:23:35 GMT
  31 + body:
  32 + encoding: UTF-8
  33 + string: '5810EEA000F1F04BA915BAF98CA700EE00 '
  34 + http_version:
  35 + recorded_at: Fri, 06 Apr 2018 19:23:33 GMT
  36 +- request:
  37 + method: get
  38 + uri: http://syspro.wildlandlabs.com:90/SYSPROWCFService/Rest/Query/Query?BusinessObject=COMFND&UserId=5810EEA000F1F04BA915BAF98CA700EE00%20%20&XmlIn=%3C?xml%20version=%221.0%22%20encoding=%22Windows-1252%22?%3E%0A%3CQuery%20xmlns:xsd=%22http://www.w3.org/2001/XMLSchema-instance%22%20xsd:noNamespaceSchemaLocation=%22COMFND.XSD%22%3E%0A%20%20%3CTableName%3EInvMaster%3C/TableName%3E%0A%20%20%3CReturnRows%3E5%3C/ReturnRows%3E%0A%20%20%3CColumns%3E%0A%20%20%20%20%0A%20%20%20%20%20%20%3CColumn%3EStockCode%3C/Column%3E%0A%20%20%20%20%0A%20%20%3C/Columns%3E%0A%20%20%0A%20%20%20%20%3CWhere%3E%0A%20%20%20%20%20%20%0A%20%20%20%20%20%20%20%20%3CExpression%3E%0A%20%20%20%20%20%20%20%20%20%20%3COpenBracket%3E(%3C/OpenBracket%3E%0A%20%20%20%20%20%20%20%20%20%20%0A%20%20%20%20%20%20%20%20%20%20%20%20%3CAndOr%3EAnd%3C/AndOr%3E%0A%20%20%20%20%20%20%20%20%20%20%0A%20%20%20%20%20%20%20%20%20%20%3CColumn%3EStockCode%3C/Column%3E%0A%20%20%20%20%20%20%20%20%20%20%3CCondition%3EEQ%3C/Condition%3E%0A%20%20%20%20%20%20%20%20%20%20%3CValue%3E02%3C/Value%3E%0A%20%20%20%20%20%20%20%20%20%20%3CCloseBracket%3E)%3C/CloseBracket%3E%0A%20%20%20%20%20%20%20%20%3C/Expression%3E%0A%20%20%20%20%20%20%0A%20%20%20%20%3C/Where%3E%0A%20%20%0A%20%20%3COrderBy%3E%0A%20%20%20%20%3CColumn%3EStockCode%3C/Column%3E%0A%20%20%3C/OrderBy%3E%0A%3C/Query%3E%0A
  39 + body:
  40 + encoding: US-ASCII
  41 + string: ''
  42 + headers:
  43 + User-Agent:
  44 + - Syspro/7 RubyBindings/0.1.0
  45 + Content-Type:
  46 + - application/x-www-form-urlencoded
  47 + Accept-Encoding:
  48 + - gzip;q=1.0,deflate;q=0.6,identity;q=0.3
  49 + Accept:
  50 + - "*/*"
  51 + response:
  52 + status:
  53 + code: 200
  54 + message: OK
  55 + headers:
  56 + Content-Length:
  57 + - '436'
  58 + Content-Type:
  59 + - application/octet-stream
  60 + Server:
  61 + - Microsoft-HTTPAPI/2.0
  62 + Date:
  63 + - Fri, 06 Apr 2018 19:23:43 GMT
  64 + body:
  65 + encoding: UTF-8
  66 + string: "<?xml version=\"1.0\" encoding=\"Windows-1252\"?>\n<InvMaster Language='05'
  67 + Language2='EN' CssStyle='' DecFormat='1' DateFormat='01' Role='01' Version='7.0.048'
  68 + OperatorPrimaryRole=' '>\n<HeaderDetails>\n<TableName>InvMaster</TableName>\n<Columns>\n<Column>StockCode</Column>\n</Columns>\n<OrderBy>\n<Column>StockCode</Column>\n</OrderBy>\n</HeaderDetails>\n<Row>\n<StockCode>02</StockCode>\n</Row>\n<RowsReturned>
  69 + \ 1</RowsReturned>\n</InvMaster>\n "
  70 + http_version:
  71 + recorded_at: Fri, 06 Apr 2018 19:23:41 GMT
  72 +recorded_with: VCR 4.0.0
test/cassettes/test_successful_logoff.yml 0 → 100644
  1 +---
  2 +http_interactions:
  3 +- request:
  4 + method: get
  5 + uri: http://syspro.wildlandlabs.com:90/SYSPROWCFService/Rest/logon?CompanyId=L&CompanyPassword=&Operator=wland&OperatorPassword=piperita2016
  6 + body:
  7 + encoding: US-ASCII
  8 + string: ''
  9 + headers:
  10 + User-Agent:
  11 + - Syspro/7 RubyBindings/0.1.0
  12 + Content-Type:
  13 + - application/x-www-form-urlencoded
  14 + Accept-Encoding:
  15 + - gzip;q=1.0,deflate;q=0.6,identity;q=0.3
  16 + Accept:
  17 + - "*/*"
  18 + response:
  19 + status:
  20 + code: 200
  21 + message: OK
  22 + headers:
  23 + Content-Length:
  24 + - '36'
  25 + Content-Type:
  26 + - application/octet-stream
  27 + Server:
  28 + - Microsoft-HTTPAPI/2.0
  29 + Date:
  30 + - Fri, 06 Apr 2018 19:22:53 GMT
  31 + body:
  32 + encoding: UTF-8
  33 + string: '989D81EB0B184A499E7AC2E28724EE9000 '
  34 + http_version:
  35 + recorded_at: Fri, 06 Apr 2018 19:22:52 GMT
  36 +- request:
  37 + method: get
  38 + uri: http://syspro.wildlandlabs.com:90/SYSPROWCFService/Rest/logoff?UserId=989D81EB0B184A499E7AC2E28724EE9000%20%20
  39 + body:
  40 + encoding: US-ASCII
  41 + string: ''
  42 + headers:
  43 + User-Agent:
  44 + - Syspro/7 RubyBindings/0.1.0
  45 + Content-Type:
  46 + - application/x-www-form-urlencoded
  47 + Accept-Encoding:
  48 + - gzip;q=1.0,deflate;q=0.6,identity;q=0.3
  49 + Accept:
  50 + - "*/*"
  51 + response:
  52 + status:
  53 + code: 200
  54 + message: OK
  55 + headers:
  56 + Content-Length:
  57 + - '1'
  58 + Content-Type:
  59 + - application/octet-stream
  60 + Server:
  61 + - Microsoft-HTTPAPI/2.0
  62 + Date:
  63 + - Fri, 06 Apr 2018 19:22:53 GMT
  64 + body:
  65 + encoding: UTF-8
  66 + string: '0'
  67 + http_version:
  68 + recorded_at: Fri, 06 Apr 2018 19:22:52 GMT
  69 +recorded_with: VCR 4.0.0
test/client_test.rb
1 -require "test_helper" 1 +# frozen_string_literal: true
  2 +
  3 +require 'test_helper'
2 4
3 class SysproClientTest < Minitest::Test 5 class SysproClientTest < Minitest::Test
  6 + extend Minitest::Spec::DSL
  7 + before { VCR.insert_cassette name }
  8 + after { VCR.eject_cassette }
  9 +
  10 + let(:client) { ::Syspro::SysproClient.new }
  11 +
4 def test_get_syspro_version 12 def test_get_syspro_version
5 - client = ::Syspro::SysproClient.new  
6 - assert_match (/(\d+\.)?(\d+\.)?(\d+\.)?(\d+)/), client.get_syspro_version.version 13 + assert_match(
  14 + /(\d+\.)?(\d+\.)?(\d+\.)?(\d+)/,
  15 + client.get_syspro_version.version
  16 + )
7 end 17 end
8 18
9 def test_client_block_execution 19 def test_client_block_execution
10 - client = ::Syspro::SysproClient.new  
11 -  
12 - version, resp = client.request { 20 + version, resp = client.request do
13 Syspro::GetVersion.get_version 21 Syspro::GetVersion.get_version
14 - } 22 + end
15 23
16 - assert_match version.version, resp.http_body  
17 - assert_match (/(\d+\.)?(\d+\.)?(\d+\.)?(\d+)/), version.version 24 + assert_match(version.version, resp.http_body)
  25 + assert_match(/(\d+\.)?(\d+\.)?(\d+\.)?(\d+)/, version.version)
18 end 26 end
19 end 27 end
test/logoff_test.rb
1 -require "test_helper" 1 +# frozen_string_literal: true
  2 +
  3 +require 'test_helper'
2 4
3 class LogoffTest < Minitest::Test 5 class LogoffTest < Minitest::Test
4 - def test_successful_logoff  
5 - username = "wland"  
6 - password = "piperita2016"  
7 - company = "L"  
8 - company_password = "" 6 + extend Minitest::Spec::DSL
  7 + before { VCR.insert_cassette name }
  8 + after { VCR.eject_cassette }
  9 +
  10 + let(:username) { 'wland' }
  11 + let(:password) { 'piperita2016' }
  12 + let(:company) { 'L' }
  13 + let(:company_password) { '' }
9 14
  15 + def test_successful_logoff
10 uid = Syspro::Logon.logon(username, password, company, company_password) 16 uid = Syspro::Logon.logon(username, password, company, company_password)
11 assert_equal true, Syspro::Logoff.logoff(uid.guid) 17 assert_equal true, Syspro::Logoff.logoff(uid.guid)
12 end 18 end
13 19
14 def test_logoff_error 20 def test_logoff_error
15 - assert_kind_of String, Syspro::Logoff.logoff('1BB5B3050954BB459A5D034DB5CC386980') 21 + assert_kind_of(
  22 + String,
  23 + Syspro::Logoff.logoff('1BB5B3050954BB459A5D034DB5CC386980')
  24 + )
16 end 25 end
17 end 26 end
18 -  
test/logon_test.rb
1 -require "test_helper" 1 +# frozen_string_literal: true
  2 +
  3 +require 'test_helper'
2 4
3 class LogonTest < Minitest::Test 5 class LogonTest < Minitest::Test
4 - def test_logon  
5 - username = "wland"  
6 - password = "piperita2016"  
7 - company = "L"  
8 - company_password = ""  
9 - client = ::Syspro::SysproClient.new 6 + extend Minitest::Spec::DSL
  7 + before { VCR.insert_cassette name }
  8 + after { VCR.eject_cassette }
  9 +
  10 + let(:username) { 'wland' }
  11 + let(:password) { 'piperita2016' }
  12 + let(:company) { 'L' }
  13 + let(:company_password) { '' }
10 14
11 - assert_match (/([A-Z0-9]{33})\w/), client.logon(username, password, company, company_password).guid 15 + def test_logon
  16 + assert_match(
  17 + /([A-Z0-9]{33})\w/,
  18 + ::Syspro::SysproClient.new.logon(
  19 + username,
  20 + password,
  21 + company,
  22 + company_password
  23 + ).guid
  24 + )
12 end 25 end
13 end 26 end
14 -  
test/query_test.rb 0 → 100644
  1 +# frozen_string_literal: true
  2 +
  3 +require 'test_helper'
  4 +
  5 +class QueryTest < Minitest::Test
  6 + extend Minitest::Spec::DSL
  7 + before { VCR.insert_cassette name }
  8 + after { VCR.eject_cassette }
  9 +
  10 + let(:username) { 'wland' }
  11 + let(:password) { 'piperita2016' }
  12 + let(:company) { 'L' }
  13 + let(:company_password) { '' }
  14 + let(:user_id) do
  15 + Syspro::Logon.logon(username, password, company, company_password)
  16 + end
  17 +
  18 + def test_query_browse # rubocop:disable Metrics/MethodLength
  19 + combrw = Syspro::BusinessObjects::ComBrw.new
  20 + combrw.browse_name = 'InvMaster'
  21 + combrw.start_condition = ''
  22 + combrw.return_rows = 5
  23 + combrw.filters = []
  24 + combrw.table_name = 'InvMaster'
  25 + combrw.title = 'StockCodes'
  26 + combrw.columns = [
  27 + { name: 'StockCode' }
  28 + ]
  29 +
  30 + browse_result = combrw.call(user_id.guid)
  31 +
  32 + refute_nil browse_result
  33 + end
  34 +
  35 + def test_query_query # rubocop:disable Metrics/MethodLength
  36 + comfnd = Syspro::BusinessObjects::ComFnd.new
  37 + comfnd.table_name = 'InvMaster'
  38 + comfnd.return_rows = 5
  39 + comfnd.columns = [
  40 + {
  41 + name: 'StockCode'
  42 + }
  43 + ]
  44 + comfnd.expressions = [
  45 + {
  46 + andor: 'And',
  47 + column: 'StockCode',
  48 + condition: 'EQ',
  49 + value: '02'
  50 + }
  51 + ]
  52 + comfnd.order_by = 'StockCode'
  53 +
  54 + query_result = comfnd.call(user_id.guid)
  55 +
  56 + refute_nil query_result
  57 + end
  58 +
  59 + def test_query_fetch
  60 + comfch = Syspro::BusinessObjects::ComFch.new
  61 + comfch.table_name = 'InvMaster'
  62 + comfch.key = '02'
  63 + comfch.optional_keys = []
  64 + comfch.full_key_provided = false
  65 + comfch.default_type = ''
  66 + comfch.espresso_fetch = true
  67 +
  68 + fetch_result = comfch.call(user_id.guid)
  69 +
  70 + refute_nil fetch_result
  71 + end
  72 +end
test/syspro_test.rb
1 -require "test_helper" 1 +# frozen_string_literal: true
  2 +
  3 +require 'test_helper'
2 4
3 class SysproTest < Minitest::Test 5 class SysproTest < Minitest::Test
4 def test_that_it_has_a_version_number 6 def test_that_it_has_a_version_number
test/test_helper.rb
1 -$LOAD_PATH.unshift File.expand_path("../../lib", __FILE__)  
2 -require "syspro" 1 +# frozen_string_literal: true
3 2
4 -require "pry"  
5 -require "minitest/autorun" 3 +$LOAD_PATH.unshift File.expand_path('../lib', __dir__)
  4 +require 'syspro'
  5 +
  6 +require 'pry'
  7 +require 'minitest/autorun'
  8 +require 'minitest-vcr'
  9 +require 'webmock'
  10 +
  11 +VCR.configure do |c|
  12 + c.cassette_library_dir = 'test/cassettes'
  13 + c.hook_into :webmock
  14 + # TODO: change passwords and move them to ENV
  15 + # c.filter_sensitive_data() { ENV[] }
  16 +end
  17 +
  18 +MinitestVcr::Spec.configure!