spec/support/imap_mock.rb in gmail-0.5.0 vs spec/support/imap_mock.rb in gmail-0.6.0
- old
+ new
@@ -1,129 +1,181 @@
-module Net
- class IMAP
- class << self
- def recordings=(value)
- @replaying = !value.nil?
- @recordings = value
- end
-
- def recordings
- @recordings ||= {}
- end
-
- def replaying?
- @replaying
- end
- end
-
- private
-
- alias_method :_send_command, :send_command
-
- def self.force_utf8(data)
- case data.class.to_s
- when /String/
- data.force_encoding('utf-8')
- when /Hash/
- data.each { |k, v| data[k] = force_utf8(v) }
- when /Array/
- data.map { |s| force_utf8(s) }
- end
- end
-
- def send_command(cmd, *args, &block)
- # In Ruby 1.9.x, strings default to binary which causes the digest to be
- # different.
- clean_args = args.dup.each do |s|
- Net::IMAP.force_utf8(s)
- end
-
- yaml_dump = YAML.dump([cmd] + clean_args)
-
- if RUBY_VERSION =~ /^(1.9|2.0)/
- # From 1.9 to 2.0 to 2.1, the way YAML encodes special characters changed.
- # Here's what each returns for: YAML.dump(["", "%"])
- # 1.9.x: "---\n- ''\n- ! '%'\n"
- # 2.0.x: "---\n- ''\n- '%'\n"
- # 2.1.x: "---\n- ''\n- \"%\"\n"
- # The `gsub` here converts the older format into the 2.1.x.
- yaml_dump.gsub!(/(?:! )?'(.+)'/, '"\1"')
-
- # In 1.9 and 2.0 strings starting with `+` or `-` are not escaped in quotes, but
- # they are in 2.1+. This addresses that.
- yaml_dump.gsub!(/ ([+-](?:X-GM-\w+|FLAGS))/, ' "\1"')
-
- # In 1.9 and 2.0 strings starting with `\` are not escaped in quotes, but
- # they are in 2.1+. This addresses that. Yes we need all those backslashes :|
- yaml_dump.gsub!(/ \\(\w+)/, ' "\\\\\\\\\1"')
- end
-
- digest = "#{cmd}-#{Digest::MD5.hexdigest(yaml_dump)}"
-
- if Net::IMAP.replaying?
- recordings = Net::IMAP.recordings[digest] || []
- if recordings.empty?
- # Be lenient if LOGOUT is called but wasn't explicitly recorded. This
- # comes up often when called from `at_exit`.
- cmd == 'LOGOUT' ? return : raise('Could not find recording')
- end
-
- action, response, @responses = recordings.shift
- else
- action = :return
- begin
- response = _send_command(cmd, *args, &block)
- rescue => e
- action = :raise
- response = e
- end
-
- # @responses (the third argument here) contains untagged responses captured
- # via the Net::IMAP#record_response method.
- Net::IMAP.recordings[digest] ||= []
- Net::IMAP.recordings[digest] << [action, response.dup, @responses ? @responses.dup : nil]
- end
-
- raise(response) if action == :raise
-
- response
- end
- end
-end
-
-module Spec
- module ImapMock
- # Configures RSpec with an around(:each) block to use IMAP mocks
- def self.configure_rspec!(config)
- config.around(:each) do |example|
- Spec::ImapMock.run_rspec_example(example)
- end
- end
-
- # Run an RSpec example using IMAP mocks
- def self.run_rspec_example(example)
- # The path is determined by the rspec `describe`s and `context`s
- mock_path = example.example_group.to_s
- .gsub(/RSpec::ExampleGroups::/, '')
- .gsub(/(\w)([A-Z])/, '\1_\2')
- .gsub(/::/, '/')
- .downcase
-
- # The name is determined by the description of the example.
- mock_name = example.description.gsub(/[^\w\-\/]+/, '_').downcase
-
- filename = File.join('spec/recordings/', mock_path, "#{mock_name}.yml")
-
- # If we've already recorded this spec load the recordings
- Net::IMAP.recordings = File.exist?(filename) ? YAML.load_file(filename) : nil
-
- example.run
-
- # If we haven't yet recorded the spec and there were some recordings,
- # write them to a file.
- unless File.exist?(filename) or Net::IMAP.recordings.empty?
- FileUtils.mkdir_p(File.dirname(filename))
- File.open(filename, 'w') { |f| YAML.dump(Net::IMAP.recordings, f) }
- end
- end
- end
-end
+module Net
+ class IMAP
+ class << self
+ def recordings=(value)
+ @replaying = !value.nil?
+ @recordings = value
+ end
+
+ def recordings
+ @recordings ||= {}
+ end
+
+ def replaying?
+ @replaying
+ end
+ end
+
+ alias_method :_idle, :idle
+ alias_method :_idle_done, :idle_done
+
+ def idle(&response_handler)
+ if Net::IMAP.replaying?
+ @idle_done_cond = new_cond
+ @idle_done = false
+ end
+
+ response = mock_command(:_idle, 'IDLE', &response_handler)
+
+ if Net::IMAP.replaying?
+ synchronize do
+ unless @idle_done
+ @idle_done_cond.wait(0.1)
+ raise('The IDLE has not done') unless @idle_done
+ end
+ @idle_done_cond = nil
+ end
+ end
+
+ response
+ end
+
+ def idle_done
+ if Net::IMAP.replaying?
+ synchronize do
+ if @idle_done_cond.nil?
+ raise Net::IMAP::Error, 'not during IDLE'
+ end
+ @idle_done = true
+ idle_done_cond.signal
+ end
+ else
+ _idle_done
+ end
+ end
+
+ private
+
+ alias_method :_send_command, :send_command
+
+ def self.force_utf8(data)
+ case data.class.to_s
+ when /String/
+ data.force_encoding('utf-8')
+ when /Hash/
+ data.each { |k, v| data[k] = force_utf8(v) }
+ when /Array/
+ data.map { |s| force_utf8(s) }
+ end
+ end
+
+ def send_command(cmd, *args, &block)
+ mock_command(:_send_command, cmd, *args, &block)
+ end
+
+ def mock_command(method, cmd, *args, &block)
+ # In Ruby 1.9.x, strings default to binary which causes the digest to be
+ # different.
+ clean_args = args.dup.each do |s|
+ Net::IMAP.force_utf8(s)
+ end
+
+ yaml_dump = YAML.dump([cmd] + clean_args)
+
+ if RUBY_VERSION =~ /^(1.9|2.0)/
+ # From 1.9 to 2.0 to 2.1, the way YAML encodes special characters changed.
+ # Here's what each returns for: YAML.dump(["", "%"])
+ # 1.9.x: "---\n- ''\n- ! '%'\n"
+ # 2.0.x: "---\n- ''\n- '%'\n"
+ # 2.1.x: "---\n- ''\n- \"%\"\n"
+ # The `gsub` here converts the older format into the 2.1.x.
+ yaml_dump.gsub!(/(?:! )?'(.+)'/, '"\1"')
+
+ # In 1.9 and 2.0 strings starting with `+` or `-` are not escaped in quotes, but
+ # they are in 2.1+. This addresses that.
+ yaml_dump.gsub!(/ ([+-](?:X-GM-\w+|FLAGS))/, ' "\1"')
+
+ # In 1.9 and 2.0 strings starting with `\` are not escaped in quotes, but
+ # they are in 2.1+. This addresses that. Yes we need all those backslashes :|
+ yaml_dump.gsub!(/ \\(\w+)/, ' "\\\\\\\\\1"')
+ end
+
+ digest = "#{cmd}-#{Digest::MD5.hexdigest(yaml_dump)}"
+
+ if Net::IMAP.replaying?
+ recordings = Net::IMAP.recordings[digest] || []
+ if recordings.empty?
+ # Be lenient if LOGOUT is called but wasn't explicitly recorded. This
+ # comes up often when called from `at_exit`.
+ cmd == 'LOGOUT' ? return : raise('Could not find recording')
+ end
+
+ action, response, @responses, all_responses = recordings.shift
+
+ if block && all_responses
+ all_responses.each do |resp|
+ block.call(resp)
+ end
+ end
+ else
+ action = :return
+ all_responses = []
+ begin
+ response = send(method, cmd, *args) do |resp|
+ all_responses << resp
+ block.call(resp)
+ end
+ rescue => e
+ action = :raise
+ response = e
+ end
+
+ # @responses (the third argument here) contains untagged responses captured
+ # via the Net::IMAP#record_response method.
+ Net::IMAP.recordings[digest] ||= []
+ Net::IMAP.recordings[digest] << [action, response.dup, @responses ? @responses.dup : nil, all_responses]
+ end
+
+ raise(response) if action == :raise
+
+ response
+ end
+ end
+end
+
+module Spec
+ module ImapMock
+ # Configures RSpec with an around(:each) block to use IMAP mocks
+ def self.configure_rspec!(config)
+ config.around(:each) do |example|
+ Spec::ImapMock.run_rspec_example(example)
+ end
+ end
+
+ # Run an RSpec example using IMAP mocks
+ def self.run_rspec_example(example)
+ # The path is determined by the rspec `describe`s and `context`s
+ mock_path = example.example_group.to_s
+ .gsub(/RSpec::ExampleGroups::/, '')
+ .gsub(/(\w)([A-Z])/, '\1_\2')
+ .gsub(/::/, '/')
+ .downcase
+
+ # The name is determined by the description of the example.
+ mock_name = example.description.gsub(/[^\w\-\/]+/, '_').downcase
+
+ filename = File.join('spec/recordings/', mock_path, "#{mock_name}.yml")
+
+ # If we've already recorded this spec load the recordings
+ Net::IMAP.recordings = File.exist?(filename) ? YAML.load_file(filename) : nil
+
+ example.run
+
+ # If we haven't yet recorded the spec and there were some recordings,
+ # write them to a file.
+ unless File.exist?(filename) or Net::IMAP.recordings.empty?
+ FileUtils.mkdir_p(File.dirname(filename))
+ File.open(filename, 'w') { |f| YAML.dump(Net::IMAP.recordings, f) }
+ end
+ end
+ end
+end