soaspec 0.1.18 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
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