soaspec 0.1.18 → 0.2.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 2d67dcecc12aa7ed80c4064617ae5b4edf939c38
4
- data.tar.gz: d38ea5011a5668761890aa5b66b93e8703f385cc
3
+ metadata.gz: bfa2bb6212fe0840b7909f7abc36b9d930130edf
4
+ data.tar.gz: 21bf07ae73063ea5b89cb893b34f4b9dd986c574
5
5
  SHA512:
6
- metadata.gz: 445f7ce4e2883bbbe784a9e2b672860f90f68c46ad9b3b2e39659efd6c81375b646fe51a5b147df5b30e807f08628b52d6a276031314901519e7d9fe1f5dbd43
7
- data.tar.gz: 8531ebc72ae98481f5d2b701b6ef719cc8ad77a94297b33469cdcb3a7ada81e4a11f21b5075e05d2e7c83bd81fe5a173fcfe520179abaa2c68fb275d5da4f24c
6
+ metadata.gz: 8da4766d8150595c4087ab1a5d4e85194a6731ccaa8720b8d25bf0f6a91d08e049da0e283f5e2ba3212e2789d74b4f4bb893b489eb33f70461bec05aa34fea2d
7
+ data.tar.gz: 7351a1edd83baa1e2dd74555ec8f8f14c5a5064be0bc81d43cf4034d41c4694eedccb5f6e637e5b2c9dfb0cccf8e4bc6738c5ca034100c02ed0f408e6f97454d
data/ChangeLog CHANGED
@@ -1,3 +1,14 @@
1
+ Version 0.2.0
2
+ * Exception handling
3
+ * Raise meaningful exception if REST header value passed is null
4
+ * Enhancements
5
+ * `soaspec add` exe to create basic 'rest' or 'soap' service
6
+ * Add another matcher to check response is successful
7
+ * Default hash and template_name can now be defined within a class rather than just at an instance of it
8
+ * Stubs making it clearer what parameters are when looking at code from RubyMine
9
+ * Basic auth file method on RestHandler for clearer defining of basic auth
10
+ * Handle ERB in 'base_url' method
11
+
1
12
  Version 0.1.18
2
13
  * Enhancements
3
14
  * Define 'exchange' method on response object so original exchange can be accessed
@@ -18,10 +18,10 @@ module Soaspec
18
18
  `soaspec new` will generate the initial files and folders for starting a testing project using soaspec
19
19
  \x5
20
20
 
21
- `soaspec new soap` will create example files testing against a virtual SOAP service
21
+ `soaspec new soap` will create example files testing against a SOAP service
22
22
  \x5
23
23
 
24
- `soaspec new rest` will create example files testing against a virtual REST service
24
+ `soaspec new rest` will create example files testing against a REST service
25
25
  LONGDESC
26
26
  desc 'new [type]', 'Initialize soaspec repository'
27
27
  option :ci, default: 'jenkins', banner: 'What Continuous Integration is used'
@@ -43,13 +43,28 @@ module Soaspec
43
43
  puts "Run 'rake spec' to run the tests"
44
44
  end
45
45
 
46
+ long_desc <<-LONGDESC
47
+ `soaspec add rest` will generate the initial files and folders for starting a testing project using soaspec
48
+ \x5
49
+
50
+ `soaspec add soap` will create example files testing against a virtual SOAP service
51
+ \x5
52
+
53
+ LONGDESC
54
+ desc 'add [type] [name]', 'Add new ExchangeHandler'
55
+ def add(type = 'rest', name = 'TestService')
56
+ raise "Type '#{type}' is not available" unless %w[rest soap].include? type
57
+ @name = name # Use instance variable for ERB
58
+ create_file(filename: File.join('lib', "#{name.snakecase}.rb"), content: retrieve_contents("lib/new_#{type}_service.rb"))
59
+ end
60
+
46
61
  desc 'generate', 'Generate initial test code from wsdl'
47
62
  long_desc <<-LONGDESC
48
63
  `soaspec generate wsdl=wsdl name=ServiceName ` will generate the initial files and folders to test each operation in a wsdl
49
64
  \x5
50
65
  Additionally the auth parameter can be used to use basic authentication to retrieve the WSDL.
51
66
  To do use the following `soaspec generate --auth=basic`
52
-
67
+ Note: This is still a work in progress and will only work for a very simple wsdl
53
68
  LONGDESC
54
69
  option :wsdl, required: true, aliases: :w
55
70
  option :name, default: 'Service', aliases: :n
@@ -1,49 +1,67 @@
1
1
  require 'active_support/core_ext/string/inflections'
2
2
 
3
+ # @return [Exchange] Return current or last exchange used in Cucumber
4
+ def current_exchange
5
+ @current_exchange ||= Soaspec.last_exchange
6
+ end
7
+
8
+ # Pass in the operation (HTTP method or SOAP operation) in first parameter and api name as second.
9
+ # API name can be mulitple words and it will be converted to camel case to find the ExchangeHandler class
3
10
  Given 'I am performing a {word} on the {string} API' do |operation, api_name|
4
- @exchange = api_name.camelize.constantize.send operation
11
+ @current_exchange = api_name.tr(' ', '_').camelize.constantize.send operation
5
12
  end
6
13
 
14
+ # Set a parameter in the request body
15
+ # @param [String] Element in request body to set
16
+ # @param [String] Value to set it to
7
17
  Given 'I set the {word} to {string}' do |key, value|
8
- @exchange[key] = value
18
+ current_exchange[key] = value
9
19
  end
10
20
 
21
+ # Add onto the base_url to make a complete url for the test
11
22
  Given 'I use the path {word}' do |suburl|
12
- @exchange.suburl = suburl
23
+ current_exchange.suburl = suburl
13
24
  end
14
25
 
26
+ # Add a query parameter for http 'get' requests. e.g. will add '?filter_key=filter_value' onto URL
15
27
  Given 'I filter {string} by {string}' do |filter_key, filter_value|
16
28
  transformed_key = filter_key.to_sym
17
- if @exchange.override_parameters[:q]
18
- @exchange.override_parameters[:q][transformed_key] = filter_value
29
+ if current_exchange.override_parameters[:q]
30
+ current_exchange.override_parameters[:q][transformed_key] = filter_value
19
31
  else
20
- @exchange.override_parameters[:q] = { transformed_key => filter_value }
32
+ current_exchange.override_parameters[:q] = { transformed_key => filter_value }
21
33
  end
22
34
  end
23
35
 
24
- # This is getting quite technical
36
+ # Add HTTP header 'header_name' as 'header_value'
25
37
  Given 'I set header {string} to {string}' do |header_name, header_value|
26
- if @exchange.override_parameters[:params]
27
- @exchange.override_parameters[:params][header_name] = header_value
38
+ if current_exchange.override_parameters[:params]
39
+ current_exchange.override_parameters[:params][header_name] = header_value
28
40
  else
29
- @exchange.override_parameters[:params] = { header_name => header_value }
41
+ current_exchange.override_parameters[:params] = { header_name => header_value }
30
42
  end
31
43
  end
32
44
 
45
+ # Make the API call. This is automatically done when any method extracting a response is made. It can be done
46
+ # explicitly here as it is a meaningful step
33
47
  When 'I make the request' do
34
- @exchange.call
48
+ current_exchange.call
35
49
  end
36
50
 
51
+ # Extract the value from the response that is at the path 'key' and verify it is eq to expected_value
37
52
  Then 'it should have the {word} {string}' do |key, expected_value|
38
- expect(@exchange[key]).to eq expected_value
53
+ expect(current_exchange[key]).to eq expected_value
39
54
  end
40
55
 
56
+ # Extract the value from the response that is at the path 'key_string' and verify it is eq to expected_value
57
+ # Conversion is made on key_string to make it one snake case word
41
58
  Then 'it should have the {string} {string}' do |key_string, expected_value|
42
59
  key = key_string.tr(' ', '_')
43
- actual_value = @exchange.respond_to?(key) ? @exchange.send(key) : @exchange[key]
60
+ actual_value = current_exchange.respond_to?(key) ? current_exchange.send(key) : current_exchange[key]
44
61
  expect(actual_value.to_s).to eq expected_value
45
62
  end
46
63
 
64
+ # Verify that the HTTP status code is 200..299 and that any defined mandatory elements / mandatory values are as expected
47
65
  Then 'it should be successful' do
48
- expect(200..299).to cover @exchange.status_code
66
+ expect(current_exchange).to be_successful
49
67
  end
@@ -1,36 +1,11 @@
1
1
  require_relative '../soaspec'
2
-
3
- # Convenience methods to set Exchange specific properties
4
- module ExchangeAccessors
5
-
6
- # Set default exchange handler for this exchange
7
- # This is helpful for when you need a new exchange handler created for each exchange
8
- def default_handler(handler_class, name = handler_class.to_s, params = '')
9
- define_method('default_handler_used') do
10
- params_used = Hash[params.map do |k, param|
11
- [k, param.is_a?(String) ? ERB.new(param).result(binding) : param]
12
- end]
13
- handler_class.new name, params_used
14
- end
15
- end
16
-
17
- # Set retry_for_success to true, retrying response until a successful status code is returned
18
- def expect_positive_status(retry_count: 3)
19
- define_method('retry_count') do
20
- retry_count
21
- end
22
- define_method('retry_for_success?') do
23
- true
24
- end
25
- end
26
-
27
- end
2
+ require_relative 'exchange_properties'
28
3
 
29
4
  # This represents a request / response pair
30
5
  # Essentially, params in the exchange that are set are related to the request
31
6
  # What is returned is related to the response
32
7
  class Exchange
33
- extend ExchangeAccessors
8
+ extend ExchangeProperties
34
9
 
35
10
  # Instance of ExchangeHandler for which this exchange is made
36
11
  attr_accessor :exchange_handler
@@ -16,18 +16,15 @@ module Soaspec
16
16
  public_methods.select { |i| i[/__custom_path_.+/] }
17
17
  end
18
18
 
19
- # Set the default hash representing data to be used in making a request
20
- # This will set the @request_option instance variable too
21
- def default_hash=(hash)
22
- @request_option = :hash
23
- @default_hash = Soaspec.always_use_keys? ? hash.transform_keys_to_symbols : hash
24
- end
25
-
26
19
  # Set instance variable name
27
20
  # @param [String, Symbol] name Name used when describing API test
28
21
  # @param [Hash] options Parameters defining handler. Used in descendants
29
22
  def initialize(name = self.class.to_s, options = {})
30
23
  use
24
+ @request_option = :hash
25
+ raise ArgumentError, 'Cannot define both template_name and default_hash' if respond_to?(:template_name_value) && respond_to?(:default_hash_value)
26
+ @template_name = respond_to?(:template_name_value) ? template_name_value : ''
27
+ @default_hash = respond_to?(:default_hash_value) ? default_hash_value : {}
31
28
  @name = name
32
29
  end
33
30
 
@@ -45,6 +42,13 @@ module Soaspec
45
42
  @name.to_s
46
43
  end
47
44
 
45
+ # Set the default hash representing data to be used in making a request
46
+ # This will set the @request_option instance variable too
47
+ def default_hash=(hash)
48
+ @request_option = :hash
49
+ @default_hash = Soaspec.always_use_keys? ? hash.transform_keys_to_symbols : hash
50
+ end
51
+
48
52
  # Set the request option type and the template name
49
53
  # Erb is used to parse the template file, executing Ruby code in `<%= %>` blocks to work out the final request
50
54
  # @param [String] name Name of file inside 'template' folder excluding extension
@@ -112,6 +116,5 @@ module Soaspec
112
116
  return "Request not yet sent Request option is #{@request_option}" unless response
113
117
  'Specific API handler should implement this'
114
118
  end
115
-
116
119
  end
117
120
  end
@@ -38,9 +38,7 @@ module Soaspec
38
38
  #
39
39
  def mandatory_xpath_values(xpath_value_pairs)
40
40
  raise ArgumentError('Hash of {xpath => expected values} expected ') unless xpath_value_pairs.is_a? Hash
41
- define_method('expected_mandatory_xpath_values') do
42
- xpath_value_pairs
43
- end
41
+ define_method('expected_mandatory_xpath_values') { xpath_value_pairs }
44
42
  end
45
43
 
46
44
  # Defines mandatory json path value pairs to be included in 'success scenario' shared example
@@ -55,9 +53,7 @@ module Soaspec
55
53
  #
56
54
  def mandatory_json_values(json_value_pairs)
57
55
  raise ArgumentError("Hash of {'jsonpath' => expected values} expected") unless json_value_pairs.is_a? Hash
58
- define_method('expected_mandatory_json_values') do
59
- json_value_pairs
60
- end
56
+ define_method('expected_mandatory_json_values') { json_value_pairs }
61
57
  end
62
58
 
63
59
  # Links a particular path to a meaningful method that can be accessed from Exchange class.
@@ -75,7 +71,7 @@ module Soaspec
75
71
  # This will use the 'value_from_path' method which
76
72
  # should be implemented by each ExchangeHandler
77
73
  # @param [String, Symbol] name Method name used to access attribute
78
- # @param [String, nil, Hash] attribute Attribute name. If not set, this will default to @name
74
+ # @param [String, nil, Hash] attribute Attribute name to extract from xml. If not set, this will default to @name
79
75
  def attribute(name, attribute = nil)
80
76
  attribute_used = attribute ? attribute : name.to_s
81
77
  define_method("__custom_path_#{name}") do |response|
@@ -87,9 +83,7 @@ module Soaspec
87
83
  # You must then use lower case in the xpath's to obtain the desired values
88
84
  def convert_to_lower(set)
89
85
  return unless set
90
- define_method('convert_to_lower?') do
91
- true
92
- end
86
+ define_method('convert_to_lower?') { true }
93
87
  end
94
88
 
95
89
  # Whether to remove namespaces from response in Xpath assertion automatically
@@ -99,8 +93,26 @@ module Soaspec
99
93
  def strip_namespaces(set)
100
94
  return unless set
101
95
  # Whether to remove namespaces in xpath assertion automatically
102
- define_method('strip_namespaces?') do
103
- true
96
+ define_method('strip_namespaces?') { true }
97
+ end
98
+
99
+ # Set the default hash representing data to be used in making a request
100
+ # This will set the @request_option instance variable too
101
+ # @param [Hash] hash Default hash of request
102
+ def default_hash(hash)
103
+ define_method('default_hash_value') do
104
+ @request_option = :hash
105
+ Soaspec.always_use_keys? ? hash.transform_keys_to_symbols : hash
106
+ end
107
+ end
108
+
109
+ # Set the request option type and the template name
110
+ # Erb is used to parse the template file, executing Ruby code in `<%= %>` blocks to work out the final request
111
+ # @param [String] name Name of file inside 'template' folder excluding extension
112
+ def template_name(name)
113
+ define_method('template_name_value') do
114
+ @request_option = :template
115
+ name
104
116
  end
105
117
  end
106
118
  end
@@ -0,0 +1,104 @@
1
+ module Soaspec
2
+ # Convenience methods for once off usage of a REST request
3
+ module RestExchangeFactory
4
+ # Make REST Exchange with 'post' method within this Handler context
5
+ # @param [Hash, String] params Exchange parameters. If String is used it will be for the request payload
6
+ # @option override_parameters [Hash] :params Extra parameters (E.g. headers)
7
+ # @option override_parameters [String] suburl URL appended to base_url of class
8
+ # @option override_parameters [Hash] :q Query for REST
9
+ # Following are for the body of the request
10
+ # @option override_parameters [Hash] :body Hash to be converted to JSON in request body
11
+ # @option override_parameters [String] :payload String to be passed directly in request body
12
+ # @option override_parameters [String] :template_name Path to file to be read via ERB and passed in request body
13
+ # @return [Exchange] Instance of Exchange class. Assertions are made by default on the response body
14
+ def post(params = {})
15
+ # This is a stub, used for indexing, source code below
16
+ end
17
+
18
+ # Make REST Exchange with 'patch' method within this Handler context
19
+ # @param [Hash, String] params Exchange parameters. If String is used it will be for the request payload
20
+ # @option override_parameters [Hash] :params Extra parameters (E.g. headers)
21
+ # @option override_parameters [String] suburl URL appended to base_url of class
22
+ # @option override_parameters [Hash] :q Query for REST
23
+ # Following are for the body of the request
24
+ # @option override_parameters [Hash] :body Hash to be converted to JSON in request body
25
+ # @option override_parameters [String] :payload String to be passed directly in request body
26
+ # @option override_parameters [String] :template_name Path to file to be read via ERB and passed in request body
27
+ # @return [Exchange] Instance of Exchange class. Assertions are made by default on the response body
28
+ def patch(params)
29
+ # This is a stub, used for indexing, source code below
30
+ end
31
+
32
+ # Make REST Exchange with 'put' method within this Handler context
33
+ # @param [Hash, String] params Exchange parameters. If String is used it will be for the request payload
34
+ # @option override_parameters [Hash] :params Extra parameters (E.g. headers)
35
+ # @option override_parameters [String] suburl URL appended to base_url of class
36
+ # @option override_parameters [Hash] :q Query for REST
37
+ # Following are for the body of the request
38
+ # @option override_parameters [Hash] :body Hash to be converted to JSON in request body
39
+ # @option override_parameters [String] :payload String to be passed directly in request body
40
+ # @option override_parameters [String] :template_name Path to file to be read via ERB and passed in request body
41
+ # @return [Exchange] Instance of Exchange class. Assertions are made by default on the response body
42
+ def put(params = {})
43
+ # This is a stub, used for indexing, source code below
44
+ end
45
+
46
+ # Make REST Exchange with 'get' method within this Handler context.
47
+ # If merely a string is passed it will be used as the URL appended to base_url. Otherwise a Hash is expected
48
+ # @param [Hash, String] params Exchange parameters. If String is used it will be for suburl
49
+ # @option override_parameters [Hash] :params Extra parameters (E.g. headers)
50
+ # @option override_parameters [String] suburl URL appended to base_url of class
51
+ # @option override_parameters [Hash] :q Query for REST
52
+ # Following are for the body of the request
53
+ # @option override_parameters [Hash] :body Hash to be converted to JSON in request body
54
+ # @option override_parameters [String] :payload String to be passed directly in request body
55
+ # @option override_parameters [String] :template_name Path to file to be read via ERB and passed in request body
56
+ # @return [Exchange] Instance of Exchange class. Assertions are made by default on the response body
57
+ def get(params = {})
58
+ # This is a stub, used for indexing, source code below
59
+ end
60
+
61
+ # Make REST Exchange with 'delete' method within this Handler context.
62
+ # If merely a string is passed it will be used as the URL appended to base_url. Otherwise a Hash is expected
63
+ # @param [Hash, String] params Exchange parameters. If String is used it will be for suburl
64
+ # @option override_parameters [Hash] :params Extra parameters (E.g. headers)
65
+ # @option override_parameters [String] suburl URL appended to base_url of class
66
+ # @option override_parameters [Hash] :q Query for REST
67
+ # Following are for the body of the request
68
+ # @option override_parameters [Hash] :body Hash to be converted to JSON in request body
69
+ # @option override_parameters [String] :payload String to be passed directly in request body
70
+ # @option override_parameters [String] :template_name Path to file to be read via ERB and passed in request body
71
+ # @return [Exchange] Instance of Exchange class. Assertions are made by default on the response body
72
+ def delete(params = {})
73
+ # This is a stub, used for indexing, source code below
74
+ end
75
+
76
+ methods = %w[post patch put get delete]
77
+
78
+ methods.each do |rest_method|
79
+ # Make REST Exchange within this Handler context
80
+ # @param [Hash, String] params Exchange parameters.
81
+ # @return [Exchange] Instance of Exchange class. Assertions are made by default on the response body
82
+ define_method(rest_method) do |params = {}|
83
+ unless params.is_a? Hash
84
+ params = case rest_method
85
+ when 'get', 'delete'
86
+ { suburl: params.to_s }
87
+ when 'post', 'put', 'patch'
88
+ { payload: params.to_s }
89
+ else
90
+ raise "'#{params}' needs to be a 'Hash' but is a #{params.class}"
91
+ end
92
+ end
93
+ params[:name] ||= rest_method
94
+ exchange_params = { name: params[:name] }
95
+ if params[:template_name]
96
+ exchange_params[:template_name] = params[:template_name]
97
+ params.delete :template_name
98
+ end
99
+ new(exchange_params)
100
+ Exchange.new(params[:name], method: rest_method.to_sym, **params)
101
+ end
102
+ end
103
+ end
104
+ end
@@ -1,5 +1,7 @@
1
1
  require_relative 'exchange_handler'
2
- require_relative 'rest_accessors'
2
+ require_relative 'rest_parameters'
3
+ require_relative 'rest_parameters_defaults'
4
+ require_relative 'rest_exchanger_factory'
3
5
  require_relative '../core_ext/hash'
4
6
  require_relative '../not_found_errors'
5
7
  require_relative 'handler_accessors'
@@ -8,13 +10,15 @@ require 'json'
8
10
  require 'jsonpath'
9
11
  require 'nori'
10
12
  require 'erb'
13
+ require 'hashie/extensions/indifferent_access'
11
14
 
12
15
  module Soaspec
13
16
 
14
17
  # Wraps around Savon client defining default values dependent on the soap request
15
18
  class RestHandler < ExchangeHandler
16
- extend Soaspec::RestAccessors
17
- include Soaspec::RestAccessorsDefaults
19
+ extend Soaspec::RestParameters
20
+ include Soaspec::RestParametersDefaults
21
+ extend Soaspec::RestExchangeFactory
18
22
 
19
23
  # User used in making API calls
20
24
  attr_accessor :api_username
@@ -23,7 +27,6 @@ module Soaspec
23
27
  # @param [Hash] options Options defining REST request. base_url, default_hash
24
28
  def initialize(name = self.class.to_s, options = {})
25
29
  raise "Base URL not set! Please set in class with 'base_url' method" unless base_url_value
26
- @default_hash = {}
27
30
  if name.is_a?(Hash) && options == {} # If name is not set, use first parameter as the options hash
28
31
  options = name
29
32
  name = self.class.to_s
@@ -31,6 +34,7 @@ module Soaspec
31
34
  super
32
35
  set_remove_keys(options, %i[api_username default_hash template_name])
33
36
  @init_options = options
37
+ init_merge_options # Call this to verify any issues with options on creating object
34
38
  end
35
39
 
36
40
  # Used in together with Exchange request that passes such override parameters
@@ -43,6 +47,7 @@ module Soaspec
43
47
  # @option override_parameters [Hash] :body Hash to be converted to JSON in request body
44
48
  # @option override_parameters [String] :payload String to be passed directly in request body
45
49
  # @option override_parameters [String] :template_name Path to file to be read via ERB and passed in request body
50
+ # @return [RestClient::Response] Response from making request
46
51
  def make_request(override_parameters)
47
52
  @merged_options ||= init_merge_options
48
53
  test_values = override_parameters
@@ -82,7 +87,10 @@ module Soaspec
82
87
  # Perform ERB on each header value
83
88
  # @return [Hash] Hash from 'rest_client_headers' passed through ERB
84
89
  def parse_headers
85
- Hash[rest_client_headers.map { |k, header| [k, ERB.new(header).result(binding)] }]
90
+ Hash[rest_client_headers.map do |header_name, header_value|
91
+ raise ArgumentError, "Header '#{header_name}' is null. Headers are #{rest_client_headers}" if header_value.nil?
92
+ [header_name, ERB.new(header_value).result(binding)]
93
+ end]
86
94
  end
87
95
 
88
96
  # Convert snakecase to PascalCase
@@ -95,6 +103,7 @@ module Soaspec
95
103
  # @return [Hash] Hash of merged options
96
104
  def init_merge_options
97
105
  options = rest_resource_options
106
+ options.merge! basic_auth_params if respond_to? :basic_auth_params
98
107
  options[:headers] ||= {}
99
108
  options[:headers].merge! parse_headers
100
109
  if Soaspec.auto_oauth && respond_to?(:access_token)
@@ -250,9 +259,10 @@ module Soaspec
250
259
  # Work out data to send based upon payload, template_name, or body
251
260
  # @return [String] Payload to send in REST request
252
261
  def post_data(test_values)
253
- data = if test_values[:body]
262
+ data = if @request_option == :hash && test_values[:body]
254
263
  test_values[:payload] = JSON.generate(hash_used_in_request(test_values[:body])).to_s
255
264
  elsif @request_option == :template
265
+ test_values = test_values[:body].dup if test_values[:body]
256
266
  Soaspec::TemplateReader.new.render_body(template_name, binding)
257
267
  else
258
268
  test_values[:payload]
@@ -270,37 +280,5 @@ module Soaspec
270
280
  request
271
281
  end
272
282
  end
273
-
274
- # Convenience methods for once off usage of a REST request
275
- class << self
276
-
277
- methods = %w[post patch put get delete]
278
-
279
- methods.each do |rest_method|
280
- # Make REST Exchange within this Handler context
281
- # @param [Hash, String] params Exchange parameters. If String is used it will be for suburl
282
- # @return [Exchange] Instance of Exchange class. Assertions are made by default on the response body
283
- define_method(rest_method) do |params = {}|
284
- unless params.is_a? Hash
285
- params = case rest_method
286
- when 'get', 'delete'
287
- { suburl: params.to_s }
288
- when 'post', 'put', 'patch'
289
- { payload: params.to_s }
290
- else
291
- params
292
- end
293
- end
294
- params[:name] ||= rest_method
295
- exchange_params = { name: params[:name] }
296
- if params[:template_name]
297
- exchange_params[:template_name] = params[:template_name]
298
- params.delete :template_name
299
- end
300
- new(exchange_params)
301
- Exchange.new(params[:name], method: rest_method.to_sym, **params)
302
- end
303
- end
304
- end
305
283
  end
306
284
  end
@@ -0,0 +1,74 @@
1
+ module Soaspec
2
+ # Methods to define parameters specific to REST handler
3
+ module RestParameters
4
+
5
+ # Defines method 'base_url_value' containing base URL used in REST requests
6
+ # @param [String] url Base Url to use in REST requests. Suburl is appended to this
7
+ def base_url(url)
8
+ raise ArgumentError, "Base Url passed must be a String for #{self} but was #{url.class}" unless url.is_a?(String)
9
+ define_method('base_url_value') { ERB.new(url).result(binding) }
10
+ end
11
+
12
+ # Will create access_token method based on passed parameters
13
+ # @param [Hash] params OAuth 2 parameters
14
+ # @param_value [token_url] URL to retrieve OAuth token from. @Note this can be set globally instead of here
15
+ # @param_value [client_id] Client ID
16
+ # @param_value [client_secret] Client Secret
17
+ # @param_value [username] Username used in password grant
18
+ # @param_value [password] Password used in password grant
19
+ # @param_value [security_token] Security Token used in password grant
20
+ def oauth2(params)
21
+ # @!method oauth_obj Object to handle oauth2
22
+ define_method('oauth_obj') { OAuth2.new(params, api_username) }
23
+ # @!method access_token Retrieve OAuth2 access token
24
+ define_method('access_token') { oauth_obj.access_token }
25
+ # @!method instance_url Retrieve instance url from OAuth request
26
+ define_method('instance_url') { oauth_obj.response['instance_url'] }
27
+ end
28
+
29
+ # Pass path to YAML file containing OAuth2 parameters
30
+ # @param [String] path_to_filename Will have Soaspec.credentials_folder appended to it if set
31
+ def oauth2_file(path_to_filename)
32
+ oauth2 load_credentials_hash(path_to_filename)
33
+ end
34
+
35
+ # Define basic authentication
36
+ # @param [String] user Username to use
37
+ # @param [String] password Password to use
38
+ def basic_auth(user: nil, password: nil)
39
+ raise ArgumentError, "Must pass both 'user' and 'password' for #{self}" unless user && password
40
+ define_method('basic_auth_params') do
41
+ { user: user, password: password }
42
+ end
43
+ end
44
+
45
+ # Pass path to YAML file containing Basic Auth parameters (i.e, both username and password)
46
+ # @param [String] path_to_filename Will have Soaspec.credentials_folder appended to it if set
47
+ def basic_auth_file(path_to_filename)
48
+ basic_auth load_credentials_hash(path_to_filename)
49
+ end
50
+
51
+ # @param [Hash] headers Hash of REST headers used in RestClient
52
+ def headers(headers)
53
+ define_method('rest_client_headers') { headers }
54
+ end
55
+
56
+ # Convert each key from snake_case to PascalCase
57
+ def pascal_keys(set)
58
+ define_method('pascal_keys?') { set }
59
+ end
60
+
61
+ private
62
+
63
+ # Load credentials hash from a YAML using Soaspec.credentials_folder if set, adding '.yml' if not set
64
+ # @return [Hash] Hash with credentials in it
65
+ def load_credentials_hash(filename)
66
+ raise ArgumentError, "Filename passed must be a String for #{self} but was #{filename.class}" unless filename.is_a?(String)
67
+ full_path = Soaspec.credentials_folder ? File.join(Soaspec.credentials_folder, filename) : filename
68
+ full_path += '.yml' unless full_path.end_with?('.yml') # Automatically add 'yml' extension
69
+ file_hash = YAML.load_file(full_path)
70
+ raise "File at #{full_path} is not a hash" unless file_hash.is_a? Hash
71
+ file_hash.transform_keys_to_symbols
72
+ end
73
+ end
74
+ end
@@ -1,6 +1,6 @@
1
1
  module Soaspec
2
- # Defaults for Soaspec RestAccessors method
3
- module RestAccessorsDefaults
2
+ # Defaults for Soaspec RestParameters methods
3
+ module RestParametersDefaults
4
4
  # Set through following method. Base URL in REST requests.
5
5
  def base_url_value
6
6
  nil
@@ -73,8 +73,6 @@ module Soaspec
73
73
  # Setup object to handle communicating with a particular SOAP WSDL
74
74
  # @param [Hash] options Options defining SOAP request. WSDL, authentication, see http://savonrb.com/version2/globals.html for list of options
75
75
  def initialize(name = self.class.to_s, options = {})
76
- @default_hash = {}
77
- @request_option = :hash
78
76
  if name.is_a?(Hash) && options == {} # If name is not set
79
77
  options = name
80
78
  name = self.class.to_s
@@ -0,0 +1,28 @@
1
+ # Convenience methods to set Exchange specific properties
2
+ # Will be used when creating a subclass of Exchange
3
+ module ExchangeProperties
4
+
5
+ # Set default exchange handler for this exchange
6
+ # This is helpful for when you need a new exchange handler created for each exchange
7
+ # @param [Class] handler_class Class of ExchangeHandler to set Exchange to use
8
+ # @param [String] name Name to call handler when it's instantiated (Defaults to class name)
9
+ # @param [Hash] params Hash of parameters to set for instance of ExchangeHandler
10
+ def default_handler(handler_class, name = handler_class.to_s, params = '')
11
+ define_method('default_handler_used') do
12
+ params_used = Hash[params.map do |k, param|
13
+ [k, param.is_a?(String) ? ERB.new(param).result(binding) : param]
14
+ end]
15
+ handler_class.new name, params_used
16
+ end
17
+ end
18
+
19
+ # Set retry_for_success to true, retrying response until a successful status code is returned
20
+ def expect_positive_status(retry_count: 3)
21
+ define_method('retry_count') do
22
+ retry_count
23
+ end
24
+ define_method('retry_for_success?') do
25
+ true
26
+ end
27
+ end
28
+ end
@@ -31,7 +31,9 @@ module Soaspec
31
31
  end
32
32
 
33
33
  # Retrieve default file contents based on filename
34
- def retrieve_contents(filename, erb)
34
+ # @param [String] filename Filename within 'lib/generator' to file retrieve contents from
35
+ # @param [Boolean] erb Whether to process file with ERB
36
+ def retrieve_contents(filename, erb = true)
35
37
  default_file = File.join(File.dirname(__FILE__), 'generator', filename + (erb ? '.erb' : ''))
36
38
  contents = File.read(default_file)
37
39
  erb ? ERB.new(contents).result(binding) : contents
@@ -0,0 +1,50 @@
1
+ # This class represent REST Api calls for the <%= @name %> API
2
+ class <%= @name %> < Soaspec::RestHandler
3
+ ## Defining request
4
+
5
+ # All requests to <%= @name %> will start with this url
6
+ # TODO: Change this mandatory base_url to the url that all requests to this service start with
7
+ # base_url "https://my_host/api/<%= ENV['environment'] %>/api_name" # ERB can be used to make this dynamic based on environment
8
+
9
+ # Headers that will be sent by default using this Handler
10
+ # If symbol is used, they'll be converted to standard HTTP headers
11
+ # headers accept: 'application/json', content_type: 'application/json'
12
+
13
+ # Filename of oauth2 file to use for oauth2 parameters. 'Soaspec.credentials_folder' can be set to globally defined a folder with credentials
14
+ # This will add a default 'Authorization: Bearer access_token' header if 'Authorization' is not specified in headers
15
+ # The '.yml' extension will be added automatically if not present.
16
+ # oauth2_file 'filename.yml'
17
+
18
+ # Filename of YAML with basic auth parameters in it 'Soaspec.credentials_folder' can be set to globally defined a folder with credentials
19
+ # It is of the format
20
+ # user: 'username'
21
+ # password: 'password'
22
+ # basic_auth_file 'basic_auth.yml' # Load YAML file with basic auth credentials in it
23
+
24
+ ### Request Body
25
+
26
+ # Filename of template to be used. This is within 'Soaspec.template_folder' folder
27
+ # template_name 'filename'
28
+
29
+ # Default hash to be used in request that will be converted to JSON. This can be overriden and added to by each Exchange using this class
30
+ # default_hash a: 1, b: 2
31
+
32
+ ## Extracting response
33
+
34
+ # Use this to extract the value of either a JSON (JSONPath) or XML (Xpath) element. 'element_name' is the method that will be
35
+ # generated on an Exchange to obtain it
36
+ # element :element_name, 'element_path'
37
+
38
+ # Use this to extract an attribute from XML. If the name of the method and the attribute name are the same then only one parameter is
39
+ # needed
40
+ # attribute(:attribute_method_name, 'name_of_attribute')
41
+
42
+ ## Creating verifications for success response
43
+
44
+ # Values that must have a certain value for 'success scenario' shared example
45
+ # mandatory_json_values '$..status' => 'available'
46
+
47
+ # Elements that must be in the response for 'success scenario' shared example
48
+ # Below will expect an element at the path 'tags' to be present
49
+ # mandatory_elements :tags
50
+ end
@@ -0,0 +1,30 @@
1
+ # This class represent SOAP Api calls for the <%= @name %> API
2
+ class <%= @name %> < Soaspec::SoapHandler
3
+ # Wsdl for <%= @name %>
4
+ def savon_options
5
+ {
6
+ # wsdl 'https://my_host/api?wsdl' TODO: Change this to the wsdl that this SOAP service uses
7
+ }
8
+ end
9
+
10
+ ## Verifying on response
11
+
12
+ # Values that must have a certain value for 'success scenario' shared example
13
+ # mandatory_json_values '$..status' => 'available'
14
+
15
+ # Elements that must be in the response for 'success scenario' shared example
16
+ # Below will expect an element at the path 'tags' to be present
17
+ # mandatory_elements :tags
18
+
19
+ # Use this to extract the value of either a JSON (JSONPath) or XML (Xpath) element. 'element_name' is the method that will be
20
+ # generated on an Exchange to obtain it
21
+ # element :element_name, 'element_path'
22
+
23
+ # Use this to extract an attribute from XML. If the name of the method and the attribute name are the same then only one parameter is
24
+ # needed
25
+ # attribute(:attribute_method_name, 'name_of_attribute')
26
+
27
+ # Set an attribute on the root XML element
28
+ # root_attributes 'Version' => '1'
29
+
30
+ end
@@ -62,5 +62,28 @@ RSpec::Matchers.define :be_found do
62
62
  failure_message do |exchange|
63
63
  "expected result #{exchange.response} to be found. Status code is #{exchange.response.code}"
64
64
  end
65
+ end
65
66
 
66
- end
67
+ # Whether response has successful status code and correct mandatory elements and values
68
+ RSpec::Matchers.define :be_successful do
69
+ match do |actual|
70
+ failure_list = []
71
+ exchange = actual.respond_to?(:exchange) ? actual.exchange : actual
72
+ failure_list << "#{exchange.status_code} not valid status code" unless (200..299).cover?(exchange.status_code)
73
+ exchange.exchange_handler.expected_mandatory_elements.each do |mandatory_element_path|
74
+ begin
75
+ exchange[mandatory_element_path]
76
+ rescue NoElementAtPath => error
77
+ failure_list << error.message
78
+ end
79
+ end
80
+ exchange.exchange_handler.expected_mandatory_xpath_values.each do |path, value|
81
+ failure_list << "Expected value at xpath '#{path}' to be '#{value}' but was '#{exchange[path]}'" unless exchange[path] == value
82
+ end
83
+ exchange.exchange_handler.expected_mandatory_json_values.each do |path, value|
84
+ failure_list << "Expected value at json '#{path}' to be '#{value}' but was '#{exchange[path]}'" unless exchange[path] == value
85
+ end
86
+ raise failure_list.to_s unless failure_list.empty?
87
+ true
88
+ end
89
+ end
@@ -17,7 +17,7 @@ shared_examples_for 'success scenario' do
17
17
  end
18
18
  end
19
19
  described_class.exchange_handler.expected_mandatory_json_values.each do |xpath, value|
20
- it "has xpath '#{xpath}' equal to '#{value}'" do
20
+ it "has json '#{xpath}' equal to '#{value}'" do
21
21
  expect(described_class).to have_xpath_value(xpath => value)
22
22
  end
23
23
  end
@@ -1,3 +1,3 @@
1
1
  module Soaspec
2
- VERSION = '0.1.18'.freeze
2
+ VERSION = '0.2.0'.freeze
3
3
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: soaspec
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.18
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - SamuelGarrattIQA
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2018-11-18 00:00:00.000000000 Z
11
+ date: 2018-11-25 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -361,11 +361,13 @@ files:
361
361
  - lib/soaspec/exchange.rb
362
362
  - lib/soaspec/exchange_handlers/exchange_handler.rb
363
363
  - lib/soaspec/exchange_handlers/handler_accessors.rb
364
- - lib/soaspec/exchange_handlers/rest_accessors.rb
365
- - lib/soaspec/exchange_handlers/rest_accessors_defaults.rb
364
+ - lib/soaspec/exchange_handlers/rest_exchanger_factory.rb
366
365
  - lib/soaspec/exchange_handlers/rest_handler.rb
367
366
  - lib/soaspec/exchange_handlers/rest_methods.rb
367
+ - lib/soaspec/exchange_handlers/rest_parameters.rb
368
+ - lib/soaspec/exchange_handlers/rest_parameters_defaults.rb
368
369
  - lib/soaspec/exchange_handlers/soap_handler.rb
370
+ - lib/soaspec/exchange_properties.rb
369
371
  - lib/soaspec/exe_helpers.rb
370
372
  - lib/soaspec/generator/.rspec.erb
371
373
  - lib/soaspec/generator/.travis.yml.erb
@@ -375,6 +377,8 @@ files:
375
377
  - lib/soaspec/generator/config/data/default.yml.erb
376
378
  - lib/soaspec/generator/lib/blz_service.rb.erb
377
379
  - lib/soaspec/generator/lib/dynamic_class_content.rb.erb
380
+ - lib/soaspec/generator/lib/new_rest_service.rb.erb
381
+ - lib/soaspec/generator/lib/new_soap_service.rb.erb
378
382
  - lib/soaspec/generator/lib/package_service.rb.erb
379
383
  - lib/soaspec/generator/lib/shared_example.rb.erb
380
384
  - lib/soaspec/generator/spec/dynamic_soap_spec.rb.erb
@@ -1,56 +0,0 @@
1
- require_relative 'rest_accessors_defaults'
2
-
3
- module Soaspec
4
- # Accessors specific to REST handler
5
- module RestAccessors
6
-
7
- # Defines method 'base_url_value' containing base URL used in REST requests
8
- # @param [String] url Base Url to use in REST requests. Suburl is appended to this
9
- def base_url(url)
10
- define_method('base_url_value') do
11
- url
12
- end
13
- end
14
-
15
- # Will create access_token method based on passed parameters
16
- # @param [Hash] params OAuth 2 parameters
17
- # @param_value [token_url] URL to retrieve OAuth token from. @Note this can be set globally instead of here
18
- # @param_value [client_id] Client ID
19
- # @param_value [client_secret] Client Secret
20
- # @param_value [username] Username used in password grant
21
- # @param_value [password] Password used in password grant
22
- # @param_value [security_token] Security Token used in password grant
23
- def oauth2(params)
24
- # @!method oauth_obj Object to handle oauth2
25
- define_method('oauth_obj') { OAuth2.new(params, api_username) }
26
- # @!method access_token Retrieve OAuth2 access token
27
- define_method('access_token') { oauth_obj.access_token }
28
- # @!method instance_url Retrieve instance url from OAuth request
29
- define_method('instance_url') { oauth_obj.response['instance_url'] }
30
- end
31
-
32
- # Pass path to YAML file containing OAuth2 parameters
33
- # @param [String] path_to_filename Will have Soaspec.credentials_folder appended to it if set
34
- def oauth2_file(path_to_filename)
35
- full_path = Soaspec.credentials_folder ? File.join(Soaspec.credentials_folder, path_to_filename) : path_to_filename
36
- full_path += '.yml' unless full_path.end_with?('.yml')
37
- file_hash = YAML.load_file(full_path)
38
- raise 'File at ' + full_path + ' is not a hash ' unless file_hash.is_a? Hash
39
- oauth2 file_hash
40
- end
41
-
42
- # @param [Hash] headers Hash of REST headers used in RestClient
43
- def headers(headers)
44
- define_method('rest_client_headers') do
45
- headers
46
- end
47
- end
48
-
49
- # Convert each key from snake_case to PascalCase
50
- def pascal_keys(set)
51
- define_method('pascal_keys?') do
52
- set
53
- end
54
- end
55
- end
56
- end