howitzer 1.0.1 → 1.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (81) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +4 -1
  3. data/.travis.yml +3 -2
  4. data/CHANGELOG.md +25 -5
  5. data/GETTING_STARTED.md +158 -13
  6. data/README.md +49 -32
  7. data/Rakefile +10 -1
  8. data/bin/howitzer +49 -78
  9. data/features/cli_help.feature +30 -0
  10. data/features/cli_new.feature +263 -0
  11. data/features/cli_unknown.feature +17 -0
  12. data/features/cli_version.feature +14 -0
  13. data/features/step_definitions/common_steps.rb +1 -0
  14. data/features/support/env.rb +1 -0
  15. data/features/support/transformers.rb +3 -0
  16. data/generators/base_generator.rb +2 -0
  17. data/generators/config/config_generator.rb +1 -1
  18. data/generators/config/templates/default.yml +4 -14
  19. data/generators/cucumber/cucumber_generator.rb +1 -1
  20. data/generators/cucumber/templates/cucumber.yml +1 -2
  21. data/generators/cucumber/templates/env.rb +8 -7
  22. data/generators/emails/emails_generator.rb +1 -1
  23. data/generators/pages/pages_generator.rb +1 -1
  24. data/generators/pages/templates/example_page.rb +2 -2
  25. data/generators/root/root_generator.rb +1 -1
  26. data/generators/root/templates/Gemfile +1 -1
  27. data/generators/rspec/rspec_generator.rb +1 -1
  28. data/generators/rspec/templates/example_spec.rb +2 -2
  29. data/generators/rspec/templates/rspec.rake +1 -1
  30. data/generators/rspec/templates/spec_helper.rb +6 -5
  31. data/generators/tasks/tasks_generator.rb +1 -1
  32. data/generators/tasks/templates/common.rake +1 -0
  33. data/howitzer.gemspec +8 -8
  34. data/lib/howitzer.rb +4 -1
  35. data/lib/howitzer/blank_page.rb +6 -0
  36. data/lib/howitzer/capybara/dsl_ex.rb +15 -0
  37. data/lib/howitzer/capybara/settings.rb +267 -0
  38. data/lib/howitzer/email.rb +134 -0
  39. data/lib/howitzer/exceptions.rb +18 -0
  40. data/lib/howitzer/helpers.rb +34 -23
  41. data/lib/howitzer/init.rb +1 -4
  42. data/lib/howitzer/mailgun/client.rb +48 -0
  43. data/lib/howitzer/mailgun/connector.rb +34 -0
  44. data/lib/howitzer/mailgun/response.rb +28 -0
  45. data/lib/howitzer/utils.rb +2 -2
  46. data/lib/howitzer/utils/data_generator/data_storage.rb +15 -2
  47. data/lib/howitzer/utils/data_generator/gen.rb +14 -10
  48. data/lib/howitzer/utils/locator_store.rb +14 -7
  49. data/lib/howitzer/utils/log.rb +2 -0
  50. data/lib/howitzer/utils/page_validator.rb +74 -27
  51. data/lib/howitzer/version.rb +1 -1
  52. data/lib/howitzer/web_page.rb +83 -32
  53. data/spec/config/default.yml +10 -12
  54. data/spec/spec_helper.rb +12 -0
  55. data/spec/support/mailgun_unit_client.rb +60 -0
  56. data/spec/unit/generators/generators_spec.rb +7 -7
  57. data/spec/unit/lib/capybara/dsl_ex_spec.rb +60 -0
  58. data/spec/unit/lib/{capybara_settings_spec.rb → capybara/settings_spec.rb} +16 -10
  59. data/spec/unit/lib/email_spec.rb +129 -0
  60. data/spec/unit/lib/helpers_spec.rb +160 -34
  61. data/spec/unit/lib/init_spec.rb +1 -12
  62. data/spec/unit/lib/mailgun/client_spec.rb +36 -0
  63. data/spec/unit/lib/mailgun/connector_spec.rb +70 -0
  64. data/spec/unit/lib/mailgun/response_spec.rb +29 -0
  65. data/spec/unit/lib/utils/data_generator/data_storage_spec.rb +23 -5
  66. data/spec/unit/lib/utils/data_generator/gen_spec.rb +2 -63
  67. data/spec/unit/lib/utils/locator_store_spec.rb +41 -6
  68. data/spec/unit/lib/utils/log_spec.rb +1 -1
  69. data/spec/unit/lib/utils/page_validator_spec.rb +149 -25
  70. data/spec/unit/lib/web_page_spec.rb +127 -53
  71. metadata +102 -142
  72. data/lib/howitzer/utils/capybara_patched.rb +0 -23
  73. data/lib/howitzer/utils/capybara_settings.rb +0 -247
  74. data/lib/howitzer/utils/email/email.rb +0 -85
  75. data/lib/howitzer/utils/email/mail_client.rb +0 -132
  76. data/lib/howitzer/utils/email/mailgun.rb +0 -175
  77. data/lib/howitzer/utils/email/mailgun_helper.rb +0 -61
  78. data/spec/unit/bin/howitzer_spec.rb +0 -175
  79. data/spec/unit/lib/utils/email/email_spec.rb +0 -75
  80. data/spec/unit/lib/utils/email/mail_client_spec.rb +0 -115
  81. data/spec/unit/lib/utils/email/mailgun_helper_spec.rb +0 -95
@@ -0,0 +1,134 @@
1
+ require 'rspec/matchers'
2
+ require 'howitzer/mailgun/connector'
3
+ require 'howitzer/exceptions'
4
+
5
+ class Email
6
+ include RSpec::Matchers
7
+
8
+ ##
9
+ #
10
+ # Creates new email with message
11
+ #
12
+ # *Parameters:*
13
+ # * +message+ - email message
14
+ #
15
+
16
+ def initialize(message)
17
+ @message = message
18
+ end
19
+
20
+ ##
21
+ #
22
+ # Search mail by recepient
23
+ #
24
+ # *Parameters:*
25
+ # * +recepient+ - recepient's email address
26
+ #
27
+
28
+ def self.find_by_recipient(recipient)
29
+ find(recipient, self::SUBJECT)
30
+ end
31
+
32
+ ##
33
+ #
34
+ # Search mail by recepient and subject.
35
+ #
36
+ # *Parameters:*
37
+ # * +recepient+ - recepient's email address
38
+ # * +subject+ - email subject
39
+ #
40
+
41
+ def self.find(recipient, subject)
42
+ message = {}
43
+ retryable(timeout: settings.timeout_small, sleep: settings.timeout_short, silent: true, logger: log, on: Howitzer::EmailNotFoundError) do
44
+ events = Mailgun::Connector.instance.client.get("#{Mailgun::Connector.instance.domain}/events", event: 'stored')
45
+ event = events.to_h['items'].find do |hash|
46
+ hash['message']['recipients'].first == recipient && hash['message']['headers']['subject'] == subject
47
+ end
48
+ if event
49
+ message = Mailgun::Connector.instance.client.get("domains/#{Mailgun::Connector.instance.domain}/messages/#{event['storage']['key']}").to_h
50
+ else
51
+ raise Howitzer::EmailNotFoundError.new('Message not received yet, retry...')
52
+ end
53
+ end
54
+ log.error Howitzer::EmailNotFoundError, "Message with subject '#{subject}' for recipient '#{recipient}' was not found." if message.empty?
55
+ new(message)
56
+ end
57
+
58
+ ##
59
+ #
60
+ # Returns plain text body of email message
61
+ #
62
+
63
+ def plain_text_body
64
+ @message['body-plain']
65
+ end
66
+
67
+ ##
68
+ #
69
+ # Returns html body of email message
70
+ #
71
+
72
+ def html_body
73
+ @message['stripped-html']
74
+ end
75
+
76
+ ##
77
+ #
78
+ # Returns mail text
79
+ #
80
+
81
+ def text
82
+ @message['stripped-text']
83
+ end
84
+
85
+ ##
86
+ #
87
+ # Returns who has send email data in format: User Name <user@email>
88
+ #
89
+
90
+ def mail_from
91
+ @message['From']
92
+ end
93
+
94
+ ##
95
+ #
96
+ # Returns array of recipients who has received current email
97
+ #
98
+
99
+ def recipients
100
+ @message['To'].split ', '
101
+ end
102
+
103
+ ##
104
+ #
105
+ # Returns email received time in format:
106
+ #
107
+
108
+ def received_time
109
+ @message['Received'][/\w+, \d+ \w+ \d+ \d+:\d+:\d+ -\d+ \(\w+\)$/]
110
+ end
111
+
112
+ ##
113
+ #
114
+ # Returns sender user email
115
+ #
116
+
117
+ def sender_email
118
+ @message['sender']
119
+ end
120
+
121
+ ##
122
+ #
123
+ # Allows to get email MIME attachment
124
+ #
125
+
126
+ def get_mime_part
127
+ files = @message['attachments']
128
+ if files.empty?
129
+ log.error Howitzer::NoAttachmentsError, 'No attachments where found.'
130
+ return
131
+ end
132
+ files
133
+ end
134
+ end
@@ -0,0 +1,18 @@
1
+ module Howitzer
2
+ CommunicationError = Class.new(StandardError)
3
+ ParseError = Class.new(StandardError)
4
+ InvalidApiKeyError = Class.new(StandardError)
5
+ LocatorNotDefinedError = Class.new(StandardError)
6
+ BadLocatorParamsError = Class.new(StandardError)
7
+ WrongOptionError = Class.new(StandardError)
8
+ NoValidationError = Class.new(StandardError)
9
+ UnknownValidationError = Class.new(StandardError)
10
+ EmailNotFoundError = Class.new(StandardError)
11
+ NoAttachmentsError = Class.new(StandardError)
12
+ DriverNotSpecifiedError = Class.new(StandardError)
13
+ SlBrowserNotSpecifiedError = Class.new(StandardError)
14
+ TbBrowserNotSpecifiedError = Class.new(StandardError)
15
+ SelBrowserNotSpecifiedError = Class.new(StandardError)
16
+ IncorrectPageError = Class.new(StandardError)
17
+ AmbiguousPageMatchingError = Class.new(StandardError)
18
+ end
@@ -1,38 +1,37 @@
1
- DriverNotSpecified = Class.new(StandardError)
2
- SlBrowserNameNotSpecified = Class.new(StandardError)
3
- TbBrowserNameNotSpecified = Class.new(StandardError)
4
- SelBrowserNotSpecified = Class.new(StandardError)
1
+ require 'howitzer/exceptions'
2
+
5
3
  CHECK_YOUR_SETTINGS_MSG = "Please check your settings"
6
- DRIVER_NOT_SPECIFIED = DriverNotSpecified.new(CHECK_YOUR_SETTINGS_MSG)
7
- SL_BROWSER_NAME_NOT_SPECIFIED = SlBrowserNameNotSpecified.new(CHECK_YOUR_SETTINGS_MSG)
8
- TB_BROWSER_NAME_NOT_SPECIFIED = TbBrowserNameNotSpecified.new(CHECK_YOUR_SETTINGS_MSG)
9
- SEL_BROWSER_NOT_SPECIFIED = SelBrowserNotSpecified.new(CHECK_YOUR_SETTINGS_MSG)
10
4
 
11
5
  def sauce_driver?
12
- raise DRIVER_NOT_SPECIFIED if settings.driver.nil?
6
+ log.error Howitzer::DriverNotSpecifiedError, CHECK_YOUR_SETTINGS_MSG if settings.driver.nil?
13
7
  settings.driver.to_sym == :sauce
14
8
  end
15
9
 
16
10
  def testingbot_driver?
17
- raise DRIVER_NOT_SPECIFIED if settings.driver.nil?
11
+ log.error Howitzer::DriverNotSpecifiedError, CHECK_YOUR_SETTINGS_MSG if settings.driver.nil?
18
12
  settings.driver.to_sym == :testingbot
19
13
  end
20
14
 
21
15
  def selenium_driver?
22
- raise DRIVER_NOT_SPECIFIED if settings.driver.nil?
16
+ log.error Howitzer::DriverNotSpecifiedError, CHECK_YOUR_SETTINGS_MSG if settings.driver.nil?
23
17
  settings.driver.to_sym == :selenium
24
18
  end
25
19
 
20
+ def phantomjs_driver?
21
+ log.error Howitzer::DriverNotSpecifiedError, CHECK_YOUR_SETTINGS_MSG if settings.driver.nil?
22
+ settings.driver.to_sym == :phantomjs
23
+ end
24
+
26
25
  def ie_browser?
27
26
  ie_browsers = [:ie, :iexplore]
28
27
  if sauce_driver?
29
- raise SL_BROWSER_NAME_NOT_SPECIFIED if settings.sl_browser_name.nil?
28
+ log.error Howitzer::SlBrowserNotSpecifiedError, CHECK_YOUR_SETTINGS_MSG if settings.sl_browser_name.nil?
30
29
  ie_browsers.include?(settings.sl_browser_name.to_sym)
31
30
  elsif testingbot_driver?
32
- raise TB_BROWSER_NAME_NOT_SPECIFIED if settings.tb_browser_name.nil?
31
+ log.error Howitzer::TbBrowserNotSpecifiedError, CHECK_YOUR_SETTINGS_MSG if settings.tb_browser_name.nil?
33
32
  ie_browsers.include?(settings.tb_browser_name.to_sym)
34
33
  elsif selenium_driver?
35
- raise SEL_BROWSER_NOT_SPECIFIED if settings.sel_browser.nil?
34
+ log.error Howitzer::SelBrowserNotSpecifiedError, CHECK_YOUR_SETTINGS_MSG if settings.sel_browser.nil?
36
35
  ie_browsers.include?(settings.sel_browser.to_sym)
37
36
  end
38
37
  end
@@ -40,13 +39,13 @@ end
40
39
  def ff_browser?
41
40
  ff_browsers = [:ff, :firefox]
42
41
  if sauce_driver?
43
- raise SL_BROWSER_NAME_NOT_SPECIFIED if settings.sl_browser_name.nil?
42
+ log.error Howitzer::SlBrowserNotSpecifiedError, CHECK_YOUR_SETTINGS_MSG if settings.sl_browser_name.nil?
44
43
  ff_browsers.include?(settings.sl_browser_name.to_sym)
45
44
  elsif testingbot_driver?
46
- raise TB_BROWSER_NAME_NOT_SPECIFIED if settings.tb_browser_name.nil?
45
+ log.error Howitzer::TbBrowserNotSpecifiedError, CHECK_YOUR_SETTINGS_MSG if settings.tb_browser_name.nil?
47
46
  ff_browsers.include?(settings.tb_browser_name.to_sym)
48
47
  elsif selenium_driver?
49
- raise SEL_BROWSER_NOT_SPECIFIED if settings.sel_browser.nil?
48
+ log.error Howitzer::SelBrowserNotSpecifiedError, CHECK_YOUR_SETTINGS_MSG if settings.sel_browser.nil?
50
49
  ff_browsers.include?(settings.sel_browser.to_sym)
51
50
  end
52
51
  end
@@ -55,13 +54,13 @@ end
55
54
  def chrome_browser?
56
55
  chrome_browser = :chrome
57
56
  if sauce_driver?
58
- raise SL_BROWSER_NAME_NOT_SPECIFIED if settings.sl_browser_name.nil?
57
+ log.error Howitzer::SlBrowserNotSpecifiedError, CHECK_YOUR_SETTINGS_MSG if settings.sl_browser_name.nil?
59
58
  settings.sl_browser_name.to_sym == chrome_browser
60
59
  elsif testingbot_driver?
61
- raise TB_BROWSER_NAME_NOT_SPECIFIED if settings.tb_browser_name.nil?
60
+ log.error Howitzer::TbBrowserNotSpecifiedError, CHECK_YOUR_SETTINGS_MSG if settings.tb_browser_name.nil?
62
61
  settings.tb_browser_name.to_sym == chrome_browser
63
62
  elsif selenium_driver?
64
- raise SEL_BROWSER_NOT_SPECIFIED if settings.sel_browser.nil?
63
+ log.error Howitzer::SelBrowserNotSpecifiedError, CHECK_YOUR_SETTINGS_MSG if settings.sel_browser.nil?
65
64
  settings.sel_browser.to_sym == chrome_browser
66
65
  end
67
66
  end
@@ -136,9 +135,12 @@ class String
136
135
  # Returns page instance
137
136
  #
138
137
 
139
-
140
138
  def given
141
- as_page_class.new
139
+ as_page_class.given
140
+ end
141
+
142
+ def wait_for_opened
143
+ as_page_class.wait_for_opened
142
144
  end
143
145
 
144
146
  ##
@@ -147,6 +149,15 @@ class String
147
149
  #
148
150
 
149
151
  def as_page_class
150
- Object.const_get("#{self.capitalize}Page")
152
+ as_class('Page')
153
+ end
154
+
155
+ def as_email_class
156
+ as_class('Email')
157
+ end
158
+
159
+ private
160
+ def as_class(type)
161
+ "#{self.gsub(/\s/, '_').camelize}#{type}".constantize
151
162
  end
152
163
  end
data/lib/howitzer/init.rb CHANGED
@@ -1,4 +1 @@
1
- include DataGenerator
2
-
3
- # Configure mail client
4
- Mailgun::init(settings.mailgun_api_key)
1
+ include DataGenerator
@@ -0,0 +1,48 @@
1
+ require 'rest_client'
2
+ require 'json'
3
+ require 'howitzer/mailgun/response'
4
+ require 'howitzer/exceptions'
5
+
6
+ module Mailgun
7
+
8
+ # A Mailgun::Client object is used to communicate with the Mailgun API. It is a
9
+ # wrapper around RestClient so you don't have to worry about the HTTP aspect
10
+ # of communicating with our API.
11
+
12
+ class Client
13
+
14
+ def initialize(api_key, api_host="api.mailgun.net", api_version="v2", ssl=true)
15
+ endpoint = endpoint_generator(api_host, api_version, ssl)
16
+ @http_client = RestClient::Resource.new(endpoint,
17
+ :user => "api",
18
+ :password => api_key,
19
+ :user_agent => "mailgun-sdk-ruby/1.0.1")
20
+ end
21
+
22
+ # Generic Mailgun GET Handler
23
+ #
24
+ # @param [String] resource_path This is the API resource you wish to interact
25
+ # with. Be sure to include your domain, where necessary.
26
+ # @param [Hash] query_string This should be a standard Hash
27
+ # containing required parameters for the requested resource.
28
+ # @return [Mailgun::Response] A Mailgun::Response object.
29
+
30
+ def get(resource_path, params=nil, accept="*/*")
31
+ http_params = {:accept => accept}
32
+ http_params = http_params.merge(params: params) if params
33
+ response = @http_client[resource_path].get(http_params)
34
+ Response.new(response)
35
+ rescue => e
36
+ log.error Howitzer::CommunicationError, e.message
37
+ end
38
+
39
+ private
40
+
41
+ def endpoint_generator(api_host, api_version, ssl)
42
+ scheme = "http#{'s' if ssl}"
43
+ res = "#{scheme}://#{api_host}"
44
+ res << "/#{api_version}" if api_version
45
+ res
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,34 @@
1
+ require 'singleton'
2
+ require 'howitzer/mailgun/client'
3
+ require 'howitzer/exceptions'
4
+
5
+ module Mailgun
6
+ class Connector
7
+ include Singleton
8
+
9
+ attr_reader :api_key
10
+
11
+ def client(api_key=settings.mailgun_key)
12
+ check_api_key(api_key)
13
+ if @api_key == api_key && @api_key
14
+ @client
15
+ else
16
+ @api_key = api_key
17
+ @client = Mailgun::Client.new(@api_key)
18
+ end
19
+ end
20
+
21
+ def domain
22
+ @domain || change_domain
23
+ end
24
+
25
+ def change_domain(domain_name=settings.mailgun_domain)
26
+ @domain = domain_name
27
+ end
28
+
29
+ private
30
+ def check_api_key(api_key)
31
+ log.error Howitzer::InvalidApiKeyError, "Api key can not be blank" if api_key.blank?
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,28 @@
1
+ require 'howitzer/exceptions'
2
+
3
+ module Mailgun
4
+ # A Mailgun::Response object is instantiated for each response generated
5
+ # by the Client request. The Response object supports deserialization of
6
+ # the JSON result.
7
+
8
+ class Response
9
+
10
+ attr_accessor :body
11
+ attr_accessor :code
12
+
13
+ def initialize(response)
14
+ @body = response.body
15
+ @code = response.code
16
+ end
17
+
18
+ # Return response as Ruby Hash
19
+ #
20
+ # @return [Hash] A standard Ruby Hash containing the HTTP result.
21
+
22
+ def to_h
23
+ JSON.parse(@body)
24
+ rescue Exception => e
25
+ log.error Howitzer::ParseError, e.message
26
+ end
27
+ end
28
+ end
@@ -1,5 +1,5 @@
1
1
  require 'repeater'
2
- require 'capybara'
3
- require 'selenium-webdriver'
2
+ require 'active_support'
3
+ require 'active_support/core_ext'
4
4
 
5
5
  Dir[File.join(File.dirname(__FILE__), "./utils/**/*.rb")].each {|f| require f}
@@ -6,10 +6,11 @@ module DataGenerator
6
6
  #
7
7
 
8
8
  module DataStorage
9
+ SPECIAL_NS_LIST = ['sauce']
9
10
  @data ||= {}
10
11
 
11
12
  class << self
12
-
13
+ attr_reader :data
13
14
  ##
14
15
  #
15
16
  # Saves data into memory. Marking by namespace and key
@@ -52,13 +53,25 @@ module DataGenerator
52
53
  init_ns(ns)
53
54
  end
54
55
 
56
+ ##
57
+ #
58
+ # Deletes all namespaces with data
59
+ #
60
+ # *Parameters:*
61
+ # * +exception_list+ - Array of special namespaces for excluding
62
+ #
63
+
64
+ def clear_all_ns(exception_list=SPECIAL_NS_LIST)
65
+ (@data.keys - exception_list).each {|ns| clear_ns(ns) }
66
+ end
67
+
55
68
  private
56
69
 
57
70
  def check_ns(ns)
58
71
  if ns
59
72
  init_ns(ns) if ns_absent?(ns)
60
73
  else
61
- raise 'Data storage namespace can not be empty'
74
+ log.error 'Data storage namespace can not be empty'
62
75
  end
63
76
  end
64
77
 
@@ -72,18 +72,18 @@ module DataGenerator
72
72
  ##
73
73
  #
74
74
  # Deletes mailboxes for all users that were generated before
75
- #
75
+ #@deprecated
76
76
 
77
+ #:nocov:
77
78
  def delete_all_mailboxes
78
- DataStorage.extract('user').each_value do |user|
79
- user.delete_mailbox
80
- end
79
+ warn "[WARN] Method '#{__method__}' has not been using anymore. You have to remove usage from here #{caller.first}"
81
80
  end
81
+ #:nocov:
82
82
 
83
83
  private
84
84
 
85
85
  def gen_user_email(serial=nil)
86
- "#{gen_user_name(serial)}@#{settings.mail_pop3_domain}"
86
+ "#{gen_user_name(serial)}@#{settings.mailgun_domain}"
87
87
  end
88
88
 
89
89
  def gen_user_name(serial=nil)
@@ -105,7 +105,7 @@ module DataGenerator
105
105
 
106
106
  class User < Object
107
107
 
108
- attr_reader :login, :domain, :email, :password, :mailbox, :first_name, :last_name, :full_name
108
+ attr_reader :login, :domain, :email, :password, :first_name, :last_name, :full_name
109
109
 
110
110
  def initialize(params={})
111
111
  @email = params.delete(:email)
@@ -115,17 +115,21 @@ module DataGenerator
115
115
  @first_name = params.delete(:first_name)
116
116
  @last_name = params.delete(:last_name)
117
117
  @full_name = "#@first_name #@last_name"
118
- @mailbox = params.delete(:mailbox)
119
118
  end
120
119
 
120
+ #@deprecated
121
+ #:nocov:
121
122
  def create_mailbox
122
- @mailbox = MailClient.create_mailbox(@email_name) if settings.mail_pop3_domain == @domain
123
- self
123
+ warn "[WARN] Method '#{__method__}' has not been using anymore. You have to remove usage from here #{caller.first}"
124
124
  end
125
+ #:nocov:
125
126
 
127
+ #@deprecated
128
+ #:nocov:
126
129
  def delete_mailbox
127
- MailClient.delete_mailbox(@mailbox) unless @mailbox.nil?
130
+ warn "[WARN] Method '#{__method__}' has not been using anymore. You have to remove usage from here #{caller.first}"
128
131
  end
132
+ #:nocov:
129
133
  end
130
134
  end
131
135
  end