diff --git a/lib/net/imap.rb b/lib/net/imap.rb
index e1ce6300..06d7bf25 100644
--- a/lib/net/imap.rb
+++ b/lib/net/imap.rb
@@ -2490,41 +2490,98 @@ def idle_done
end
end
+ RESPONSES_DEPRECATION_MSG =
+ "Pass a type or block to #responses, " \
+ "set config.responses_without_block to :frozen_dup " \
+ "or :silence_deprecation_warning, " \
+ "or use #extract_responses or #clear_responses."
+ private_constant :RESPONSES_DEPRECATION_MSG
+
# :call-seq:
+ # responses -> hash of {String => Array} (see config.responses_without_block)
+ # responses(type) -> frozen array
# responses {|hash| ...} -> block result
# responses(type) {|array| ...} -> block result
#
- # Yields unhandled responses and returns the result of the block.
+ # Yields or returns unhandled server responses. Unhandled responses are
+ # stored in a hash, with arrays of UntaggedResponse#data keyed by
+ # UntaggedResponse#name and non-+nil+ untagged ResponseCode#data
+ # keyed by ResponseCode#name.
+ #
+ # When a block is given, yields unhandled responses and returns the block's
+ # result. Without a block, returns the unhandled responses.
+ #
+ # [With +type+]
+ # Yield or return only the array of responses for that +type+.
+ # When no block is given, the returned array is a frozen copy.
+ # [Without +type+]
+ # Yield or return the entire responses hash.
+ #
+ # When no block is given, the behavior is determined by
+ # Config#responses_without_block:
+ # >>>
+ # [+:silence_deprecation_warning+ (original behavior)]
+ # Returns the mutable responses hash (without any warnings).
+ # This is not thread-safe.
+ #
+ # [+:warn+ (default since +v0.5+)]
+ # Prints a warning and returns the mutable responses hash.
+ # This is not thread-safe.
+ #
+ # [+:frozen_dup+ (planned default for +v0.6+)]
+ # Returns a frozen copy of the unhandled responses hash, with frozen
+ # array values.
#
- # Unhandled responses are stored in a hash, with arrays of
- # non-+nil+ UntaggedResponse#data keyed by UntaggedResponse#name
- # and ResponseCode#data keyed by ResponseCode#name. Call without +type+ to
- # yield the entire responses hash. Call with +type+ to yield only the array
- # of responses for that type.
+ # [+:raise+]
+ # Raise an +ArgumentError+ with the deprecation warning.
#
# For example:
#
# imap.select("inbox")
- # p imap.responses("EXISTS", &:last)
+ # p imap.responses("EXISTS").last
# #=> 2
+ # p imap.responses("UIDNEXT", &:last)
+ # #=> 123456
# p imap.responses("UIDVALIDITY", &:last)
# #=> 968263756
+ # p imap.responses {|responses|
+ # {
+ # exists: responses.delete("EXISTS").last,
+ # uidnext: responses.delete("UIDNEXT").last,
+ # uidvalidity: responses.delete("UIDVALIDITY").last,
+ # }
+ # }
+ # #=> {:exists=>2, :uidnext=>123456, :uidvalidity=>968263756}
+ # # "EXISTS", "UIDNEXT", and "UIDVALIDITY" have been removed:
+ # p imap.responses(&:keys)
+ # #=> ["FLAGS", "OK", "PERMANENTFLAGS", "RECENT", "HIGHESTMODSEQ"]
+ #
+ # Related: #extract_responses, #clear_responses, #response_handlers, #greeting
#
+ # ===== Thread safety
# >>>
# *Note:* Access to the responses hash is synchronized for thread-safety.
# The receiver thread and response_handlers cannot process new responses
# until the block completes. Accessing either the response hash or its
- # response type arrays outside of the block is unsafe.
+ # response type arrays outside of the block is unsafe. They can be safely
+ # updated inside the block. Consider using #clear_responses or
+ # #extract_responses instead.
+ #
+ # Net::IMAP will add and remove responses from the responses hash and its
+ # array values, in the calling threads for commands and in the receiver
+ # thread, but will not modify any responses after adding them to the
+ # responses hash.
#
- # Calling without a block is unsafe and deprecated. Future releases will
- # raise ArgumentError unless a block is given.
- # See Config#responses_without_block.
+ # ===== Clearing responses
#
# Previously unhandled responses are automatically cleared before entering a
# mailbox with #select or #examine. Long-lived connections can receive many
# unhandled server responses, which must be pruned or they will continually
# consume more memory. Update or clear the responses hash or arrays inside
- # the block, or use #clear_responses.
+ # the block, or remove responses with #extract_responses, #clear_responses,
+ # or #add_response_handler.
+ #
+ # ===== Missing responses
#
# Only non-+nil+ data is stored. Many important response codes have no data
# of their own, but are used as "tags" on the ResponseText object they are
@@ -2535,20 +2592,24 @@ def idle_done
# ResponseCode#data on tagged responses. Although some command methods do
# return the TaggedResponse directly, #add_response_handler must be used to
# handle all response codes.
- #
- # Related: #extract_responses, #clear_responses, #response_handlers, #greeting
def responses(type = nil)
if block_given?
synchronize { yield(type ? @responses[type.to_s.upcase] : @responses) }
elsif type
- raise ArgumentError, "Pass a block or use #clear_responses"
+ synchronize { @responses[type.to_s.upcase].dup.freeze }
else
case config.responses_without_block
when :raise
- raise ArgumentError, "Pass a block or use #clear_responses"
+ raise ArgumentError, RESPONSES_DEPRECATION_MSG
when :warn
- warn("DEPRECATED: pass a block or use #clear_responses",
- uplevel: 1, category: :deprecated)
+ warn(RESPONSES_DEPRECATION_MSG, uplevel: 1, category: :deprecated)
+ when :frozen_dup
+ synchronize {
+ responses = @responses.transform_values(&:freeze)
+ responses.default_proc = nil
+ responses.default = [].freeze
+ return responses.freeze
+ }
end
@responses
end
diff --git a/lib/net/imap/config.rb b/lib/net/imap/config.rb
index bcf5b72d..7532737c 100644
--- a/lib/net/imap/config.rb
+++ b/lib/net/imap/config.rb
@@ -7,10 +7,10 @@
module Net
class IMAP
- # Net::IMAP::Config stores configuration options for Net::IMAP clients.
- # The global configuration can be seen at either Net::IMAP.config or
- # Net::IMAP::Config.global, and the client-specific configuration can be
- # seen at Net::IMAP#config.
+ # Net::IMAP::Config (available since +v0.4.13+) stores
+ # configuration options for Net::IMAP clients. The global configuration can
+ # be seen at either Net::IMAP.config or Net::IMAP::Config.global, and the
+ # client-specific configuration can be seen at Net::IMAP#config.
#
# When creating a new client, all unhandled keyword arguments to
# Net::IMAP.new are delegated to Config.new. Every client has its own
@@ -128,7 +128,7 @@ def self.default; @default end
# The global config object. Also available from Net::IMAP.config.
def self.global; @global if defined?(@global) end
- # A hash of hard-coded configurations, indexed by version number.
+ # A hash of hard-coded configurations, indexed by version number or name.
def self.version_defaults; @version_defaults end
@version_defaults = {}
@@ -172,9 +172,16 @@ def self.[](config)
include AttrInheritance
include AttrTypeCoercion
- # The debug mode (boolean)
+ # The debug mode (boolean). The default value is +false+.
#
- # The default value is +false+.
+ # When #debug is +true+:
+ # * Data sent to and received from the server will be logged.
+ # * ResponseParser will print warnings with extra detail for parse
+ # errors. _This may include recoverable errors._
+ # * ResponseParser makes extra assertions.
+ #
+ # *NOTE:* Versioned default configs inherit #debug from Config.global, and
+ # #load_defaults will not override #debug.
attr_accessor :debug, type: :boolean
# method: debug?
@@ -200,60 +207,84 @@ def self.[](config)
# The default value is +5+ seconds.
attr_accessor :idle_response_timeout, type: Integer
- # :markup: markdown
- #
# Whether to use the +SASL-IR+ extension when the server and \SASL
- # mechanism both support it.
+ # mechanism both support it. Can be overridden by the +sasl_ir+ keyword
+ # parameter to Net::IMAP#authenticate.
+ #
+ # (Support for +SASL-IR+ was added in +v0.4.0+.)
#
- # See Net::IMAP#authenticate.
+ # ==== Valid options
#
- # | Starting with version | The default value is |
- # |-----------------------|------------------------------------------|
- # | _original_ | +false+ (extension unsupported) |
- # | v0.4 | +true+ (support added) |
+ # [+false+ (original behavior, before support was added)]
+ # Do not use +SASL-IR+, even when it is supported by the server and the
+ # mechanism.
+ #
+ # [+true+ (default since +v0.4+)]
+ # Use +SASL-IR+ when it is supported by the server and the mechanism.
attr_accessor :sasl_ir, type: :boolean
- # :markup: markdown
- #
- # Controls the behavior of Net::IMAP#login when the `LOGINDISABLED`
+ # Controls the behavior of Net::IMAP#login when the +LOGINDISABLED+
# capability is present. When enforced, Net::IMAP will raise a
- # LoginDisabledError when that capability is present. Valid values are:
+ # LoginDisabledError when that capability is present.
#
- # [+false+]
+ # (Support for +LOGINDISABLED+ was added in +v0.5.0+.)
+ #
+ # ==== Valid options
+ #
+ # [+false+ (original behavior, before support was added)]
# Send the +LOGIN+ command without checking for +LOGINDISABLED+.
#
# [+:when_capabilities_cached+]
# Enforce the requirement when Net::IMAP#capabilities_cached? is true,
# but do not send a +CAPABILITY+ command to discover the capabilities.
#
- # [+true+]
+ # [+true+ (default since +v0.5+)]
# Only send the +LOGIN+ command if the +LOGINDISABLED+ capability is not
# present. When capabilities are unknown, Net::IMAP will automatically
# send a +CAPABILITY+ command first before sending +LOGIN+.
#
- # | Starting with version | The default value is |
- # |-------------------------|--------------------------------|
- # | _original_ | `false` |
- # | v0.5 | `true` |
attr_accessor :enforce_logindisabled, type: [
false, :when_capabilities_cached, true
]
- # :markup: markdown
+ # Controls the behavior of Net::IMAP#responses when called without any
+ # arguments (+type+ or +block+).
+ #
+ # ==== Valid options
+ #
+ # [+:silence_deprecation_warning+ (original behavior)]
+ # Returns the mutable responses hash (without any warnings).
+ # This is not thread-safe.
+ #
+ # [+:warn+ (default since +v0.5+)]
+ # Prints a warning and returns the mutable responses hash.
+ # This is not thread-safe.
#
- # Controls the behavior of Net::IMAP#responses when called without a
- # block. Valid options are `:warn`, `:raise`, or
- # `:silence_deprecation_warning`.
+ # [+:frozen_dup+ (planned default for +v0.6+)]
+ # Returns a frozen copy of the unhandled responses hash, with frozen
+ # array values.
#
- # | Starting with version | The default value is |
- # |-------------------------|--------------------------------|
- # | v0.4.13 | +:silence_deprecation_warning+ |
- # | v0.5 | +:warn+ |
- # | _eventually_ | +:raise+ |
+ # Note that calling IMAP#responses with a +type+ and without a block is
+ # not configurable and always behaves like +:frozen_dup+.
+ #
+ # (+:frozen_dup+ config option was added in +v0.4.17+)
+ #
+ # [+:raise+]
+ # Raise an ArgumentError with the deprecation warning.
+ #
+ # Note: #responses_without_args is an alias for #responses_without_block.
attr_accessor :responses_without_block, type: [
- :silence_deprecation_warning, :warn, :raise,
+ :silence_deprecation_warning, :warn, :frozen_dup, :raise,
]
+ alias responses_without_args responses_without_block # :nodoc:
+ alias responses_without_args= responses_without_block= # :nodoc:
+
+ ##
+ # :attr_accessor: responses_without_args
+ #
+ # Alias for responses_without_block
+
# Creates a new config object and initialize its attribute with +attrs+.
#
# If +parent+ is not given, the global config is used by default.
@@ -357,12 +388,11 @@ def defaults_hash
version_defaults[0.5] = Config[:current]
- version_defaults[0.6] = Config[0.5]
- version_defaults[:next] = Config[0.6]
-
- version_defaults[:future] = Config[0.6].dup.update(
- responses_without_block: :raise,
+ version_defaults[0.6] = Config[0.5].dup.update(
+ responses_without_block: :frozen_dup,
).freeze
+ version_defaults[:next] = Config[0.6]
+ version_defaults[:future] = Config[:next]
version_defaults.freeze
end
diff --git a/test/lib/helper.rb b/test/lib/helper.rb
index bef82346..e84854a5 100644
--- a/test/lib/helper.rb
+++ b/test/lib/helper.rb
@@ -2,3 +2,16 @@
require "core_assertions"
Test::Unit::TestCase.include Test::Unit::CoreAssertions
+
+class Test::Unit::TestCase
+ def wait_for_response_count(imap, type:, count:,
+ timeout: 0.5, interval: 0.001)
+ deadline = Time.now + timeout
+ loop do
+ current_count = imap.responses(type, &:size)
+ break :count if count <= current_count
+ break :deadline if deadline < Time.now
+ sleep interval
+ end
+ end
+end
diff --git a/test/net/imap/test_config.rb b/test/net/imap/test_config.rb
index 6c709735..fa11c74a 100644
--- a/test/net/imap/test_config.rb
+++ b/test/net/imap/test_config.rb
@@ -190,7 +190,7 @@ class ConfigTest < Test::Unit::TestCase
assert_same Config.default, Config.new(Config.default).parent
assert_same Config.global, Config.new(Config.global).parent
assert_same Config[0.4], Config.new(0.4).parent
- assert_same Config[0.5], Config.new(:next).parent
+ assert_same Config[0.6], Config.new(:next).parent
assert_equal true, Config.new({debug: true}, debug: false).parent.debug?
assert_equal true, Config.new({debug: true}, debug: false).parent.frozen?
end
diff --git a/test/net/imap/test_imap.rb b/test/net/imap/test_imap.rb
index 97e9a14d..6c30323b 100644
--- a/test/net/imap/test_imap.rb
+++ b/test/net/imap/test_imap.rb
@@ -1110,107 +1110,6 @@ def test_enable
end
end
- test "#responses" do
- with_fake_server do |server, imap|
- # responses available before SELECT/EXAMINE
- assert_equal(%w[IMAP4REV1 NAMESPACE MOVE IDLE UTF8=ACCEPT],
- imap.responses("CAPABILITY", &:last))
- resp = imap.select "INBOX"
- # responses are cleared after SELECT/EXAMINE
- assert_equal(nil, imap.responses("CAPABILITY", &:last))
- assert_equal([Net::IMAP::TaggedResponse, "RUBY0001", "OK"],
- [resp.class, resp.tag, resp.name])
- assert_equal([172], imap.responses { _1["EXISTS"] })
- assert_equal([3857529045], imap.responses("UIDVALIDITY") { _1 })
- assert_equal(1, imap.responses("RECENT", &:last))
- assert_raise(ArgumentError) do imap.responses("UIDNEXT") end
- # Deprecated style, without a block:
- imap.config.responses_without_block = :raise
- assert_raise(ArgumentError) do imap.responses end
- imap.config.responses_without_block = :warn
- assert_raise(ArgumentError) do imap.responses("UIDNEXT") end
- assert_warn(/Pass a block.*or.*clear_responses/i) do
- assert_equal(%i[Answered Flagged Deleted Seen Draft],
- imap.responses["FLAGS"]&.last)
- end
- # TODO: assert_no_warn?
- imap.config.responses_without_block = :silence_deprecation_warning
- assert_raise(ArgumentError) do imap.responses("UIDNEXT") end
- stderr = EnvUtil.verbose_warning {
- assert_equal(%i[Answered Flagged Deleted Seen Draft],
- imap.responses["FLAGS"]&.last)
- }
- assert_empty stderr
- end
- end
-
- test "#clear_responses" do
- with_fake_server do |server, imap|
- resp = imap.select "INBOX"
- assert_equal([Net::IMAP::TaggedResponse, "RUBY0001", "OK"],
- [resp.class, resp.tag, resp.name])
- # called with "type", clears and returns only that type
- assert_equal([172], imap.clear_responses("EXISTS"))
- assert_equal([], imap.clear_responses("EXISTS"))
- assert_equal([1], imap.clear_responses("RECENT"))
- assert_equal([3857529045], imap.clear_responses("UIDVALIDITY"))
- # called without "type", clears and returns all responses
- responses = imap.clear_responses
- assert_equal([], responses["EXISTS"])
- assert_equal([], responses["RECENT"])
- assert_equal([], responses["UIDVALIDITY"])
- assert_equal([12], responses["UNSEEN"])
- assert_equal([4392], responses["UIDNEXT"])
- assert_equal(5, responses["FLAGS"].last&.size)
- assert_equal(3, responses["PERMANENTFLAGS"].last&.size)
- assert_equal({}, imap.responses(&:itself))
- assert_equal({}, imap.clear_responses)
- end
- end
-
- test "#extract_responses" do
- with_fake_server do |server, imap|
- resp = imap.select "INBOX"
- assert_equal([Net::IMAP::TaggedResponse, "RUBY0001", "OK"],
- [resp.class, resp.tag, resp.name])
- # Need to send a string type and a block
- assert_raise(ArgumentError) do imap.extract_responses { true } end
- assert_raise(ArgumentError) do imap.extract_responses(nil) { true } end
- assert_raise(ArgumentError) do imap.extract_responses("OK") end
- # matching nothing
- assert_equal([172], imap.responses("EXISTS", &:dup))
- assert_equal([], imap.extract_responses("EXISTS") { String === _1 })
- assert_equal([172], imap.responses("EXISTS", &:dup))
- # matching everything
- assert_equal([172], imap.responses("EXISTS", &:dup))
- assert_equal([172], imap.extract_responses("EXISTS", &:even?))
- assert_equal([], imap.responses("EXISTS", &:dup))
- # matching some
- server.unsolicited("101 FETCH (UID 1111 FLAGS (\\Seen))")
- server.unsolicited("102 FETCH (UID 2222 FLAGS (\\Seen \\Flagged))")
- server.unsolicited("103 FETCH (UID 3333 FLAGS (\\Deleted))")
- wait_for_response_count(imap, type: "FETCH", count: 3)
-
- result = imap.extract_responses("FETCH") { _1.flags.include?(:Flagged) }
- assert_equal(
- [
- Net::IMAP::FetchData.new(
- 102, {"UID" => 2222, "FLAGS" => [:Seen, :Flagged]}
- ),
- ],
- result,
- )
- assert_equal 2, imap.responses("FETCH", &:count)
-
- result = imap.extract_responses("FETCH") { _1.flags.include?(:Deleted) }
- assert_equal(
- [Net::IMAP::FetchData.new(103, {"UID" => 3333, "FLAGS" => [:Deleted]})],
- result
- )
- assert_equal 1, imap.responses("FETCH", &:count)
- end
- end
-
test "#select with condstore" do
with_fake_server do |server, imap|
imap.select "inbox", condstore: true
@@ -1467,15 +1366,4 @@ def server_addr
Addrinfo.tcp("localhost", 0).ip_address
end
- def wait_for_response_count(imap, type:, count:,
- timeout: 0.5, interval: 0.001)
- deadline = Time.now + timeout
- loop do
- current_count = imap.responses(type, &:size)
- break :count if count <= current_count
- break :deadline if deadline < Time.now
- sleep interval
- end
- end
-
end
diff --git a/test/net/imap/test_imap_responses.rb b/test/net/imap/test_imap_responses.rb
new file mode 100644
index 00000000..aaa61d43
--- /dev/null
+++ b/test/net/imap/test_imap_responses.rb
@@ -0,0 +1,248 @@
+# frozen_string_literal: true
+
+require "net/imap"
+require "test/unit"
+require_relative "fake_server"
+
+class IMAPResponsesTest < Test::Unit::TestCase
+ include Net::IMAP::FakeServer::TestHelper
+
+ CONFIG_OPTIONS = %i[
+ silence_deprecation_warning
+ warn
+ raise
+ ].freeze
+
+ def setup
+ Net::IMAP.config.reset
+ @do_not_reverse_lookup = Socket.do_not_reverse_lookup
+ Socket.do_not_reverse_lookup = true
+ @threads = []
+ end
+
+ def teardown
+ if !@threads.empty?
+ assert_join_threads(@threads)
+ end
+ ensure
+ Socket.do_not_reverse_lookup = @do_not_reverse_lookup
+ end
+
+ def for_each_config_option(imap)
+ original = imap.config.responses_without_block
+ CONFIG_OPTIONS.each do |option|
+ imap.config.responses_without_block = option
+ yield option
+ end
+ ensure
+ imap.config.responses_without_block = original
+ end
+
+ # with a block: returns the block result
+ test "#responses(&block)" do
+ with_fake_server do |server, imap|
+ stderr = EnvUtil.verbose_warning do
+ # Config options make no difference to responses(&block)
+ for_each_config_option(imap) do
+ # responses available before SELECT/EXAMINE
+ assert_equal(%w[IMAP4REV1 NAMESPACE MOVE IDLE UTF8=ACCEPT],
+ imap.responses { _1["CAPABILITY"].last })
+ end
+ # responses are cleared after SELECT/EXAMINE
+ imap.select "INBOX"
+ for_each_config_option(imap) do
+ assert_equal nil, imap.responses { _1["CAPABILITY"].last }
+ assert_equal [172], imap.responses { _1["EXISTS"].dup }
+ assert_equal [3857529045], imap.responses { _1["UIDVALIDITY"].dup }
+ assert_equal 1, imap.responses { _1["RECENT"].last }
+ assert_equal(%i[Answered Flagged Deleted Seen Draft],
+ imap.responses { _1["FLAGS"].last })
+ end
+ end
+ assert_empty stderr # never warn when a block is given
+ end
+ end
+
+ # with a type and a block: returns the block result
+ test "#responses(type, &block)" do
+ with_fake_server do |server, imap|
+ stderr = EnvUtil.verbose_warning do
+ # Config options make no difference to responses(type, &block)
+ for_each_config_option(imap) do
+ # responses available before SELECT/EXAMINE
+ assert_equal(%w[IMAP4REV1 NAMESPACE MOVE IDLE UTF8=ACCEPT],
+ imap.responses("CAPABILITY", &:last))
+ end
+ # responses are cleared after SELECT/EXAMINE
+ imap.select "INBOX"
+ for_each_config_option(imap) do
+ assert_equal nil, imap.responses("CAPABILITY", &:last)
+ assert_equal [172], imap.responses("EXISTS", &:dup)
+ assert_equal [3857529045], imap.responses("UIDVALIDITY", &:dup)
+ assert_equal 1, imap.responses("RECENT", &:last)
+ assert_equal [4392], imap.responses("UIDNEXT", &:dup)
+ assert_equal(%i[Answered Flagged Deleted Seen Draft],
+ imap.responses("FLAGS", &:last))
+ end
+ end
+ assert_empty stderr # never warn when type or block are given
+ end
+ end
+
+ # with with a type and no block: always returns a frozen duplicate
+ test "#responses(type, &nil)" do
+ with_fake_server do |server, imap|
+ stderr = EnvUtil.verbose_warning do
+ # Config options make no difference to responses(type)
+ for_each_config_option(imap) do
+ # responses available before SELECT/EXAMINE
+ assert imap.responses("CAPABILITY").frozen?
+ assert_equal(%w[IMAP4REV1 NAMESPACE MOVE IDLE UTF8=ACCEPT],
+ imap.responses("CAPABILITY").last)
+ end
+ # responses are cleared after SELECT/EXAMINE
+ imap.select "INBOX"
+ for_each_config_option(imap) do
+ assert imap.responses("CAPABILITY").frozen?
+ assert imap.responses("EXISTS").frozen?
+ assert imap.responses("UIDVALIDITIY").frozen?
+ assert_equal [], imap.responses("CAPABILITY")
+ assert_equal [172], imap.responses("EXISTS")
+ assert_equal [3857529045], imap.responses("UIDVALIDITY")
+ assert_equal 1, imap.responses("RECENT").last
+ assert imap.responses("UIDNEXT").frozen?
+ assert_equal [4392], imap.responses("UIDNEXT")
+ assert imap.responses("FLAGS").frozen?
+ assert_equal(%i[Answered Flagged Deleted Seen Draft],
+ imap.responses("FLAGS").last)
+ end
+ end
+ assert_empty stderr # never warn when type is given
+ end
+ end
+
+ def assert_responses_warn
+ assert_warn(
+ /
+ (?=(?-x)Pass a type or block to #responses\b)
+ (?=.*config\.responses_without_block.*:silence_deprecation_warning\b)
+ (?=.*\#extract_responses\b)
+ .*\#clear_responses\b
+ /ix
+ ) do
+ yield
+ end
+ end
+
+ # without type or block: relies on config.responses_without_block
+ test "#responses without type or block" do
+ with_fake_server do |server, imap|
+ # can be configured to raise
+ imap.config.responses_without_block = :raise
+ assert_raise(ArgumentError) do imap.responses end
+ # with warnings (default for v0.5)
+ imap.config.responses_without_block = :warn
+ assert_responses_warn do assert_kind_of Hash, imap.responses end
+ assert_responses_warn do refute imap.responses.frozen? end
+ assert_responses_warn do refute imap.responses["CAPABILITY"].frozen? end
+ assert_responses_warn do
+ assert_equal(%w[IMAP4REV1 NAMESPACE MOVE IDLE UTF8=ACCEPT],
+ imap.responses["CAPABILITY"].last)
+ end
+ assert_responses_warn do imap.responses["FAKE"] = :uh_oh! end
+ assert_responses_warn do assert_equal :uh_oh!, imap.responses["FAKE"] end
+ assert_responses_warn do imap.responses.delete("FAKE") end
+ assert_responses_warn do assert_equal [], imap.responses["FAKE"] end
+ # warnings can be silenced
+ imap.config.responses_without_block = :silence_deprecation_warning
+ stderr = EnvUtil.verbose_warning do
+ refute imap.responses.frozen?
+ refute imap.responses["CAPABILITY"].frozen?
+ assert_equal(%w[IMAP4REV1 NAMESPACE MOVE IDLE UTF8=ACCEPT],
+ imap.responses["CAPABILITY"].last)
+ imap.responses["FAKE"] = :uh_oh!
+ assert_equal :uh_oh!, imap.responses["FAKE"]
+ imap.responses.delete("FAKE")
+ assert_equal [], imap.responses["FAKE"]
+ end
+ assert_empty stderr
+ # opt-in to future behavior
+ imap.config.responses_without_block = :frozen_dup
+ stderr = EnvUtil.verbose_warning do
+ assert imap.responses.frozen?
+ assert imap.responses["CAPABILITY"].frozen?
+ assert_equal(%w[IMAP4REV1 NAMESPACE MOVE IDLE UTF8=ACCEPT],
+ imap.responses["CAPABILITY"].last)
+ end
+ assert_empty stderr
+ end
+ end
+
+ test "#clear_responses" do
+ with_fake_server do |server, imap|
+ resp = imap.select "INBOX"
+ assert_equal([Net::IMAP::TaggedResponse, "RUBY0001", "OK"],
+ [resp.class, resp.tag, resp.name])
+ # called with "type", clears and returns only that type
+ assert_equal([172], imap.clear_responses("EXISTS"))
+ assert_equal([], imap.clear_responses("EXISTS"))
+ assert_equal([1], imap.clear_responses("RECENT"))
+ assert_equal([3857529045], imap.clear_responses("UIDVALIDITY"))
+ # called without "type", clears and returns all responses
+ responses = imap.clear_responses
+ assert_equal([], responses["EXISTS"])
+ assert_equal([], responses["RECENT"])
+ assert_equal([], responses["UIDVALIDITY"])
+ assert_equal([12], responses["UNSEEN"])
+ assert_equal([4392], responses["UIDNEXT"])
+ assert_equal(5, responses["FLAGS"].last&.size)
+ assert_equal(3, responses["PERMANENTFLAGS"].last&.size)
+ assert_equal({}, imap.responses(&:itself))
+ assert_equal({}, imap.clear_responses)
+ end
+ end
+
+ test "#extract_responses" do
+ with_fake_server do |server, imap|
+ resp = imap.select "INBOX"
+ assert_equal([Net::IMAP::TaggedResponse, "RUBY0001", "OK"],
+ [resp.class, resp.tag, resp.name])
+ # Need to send a string type and a block
+ assert_raise(ArgumentError) do imap.extract_responses { true } end
+ assert_raise(ArgumentError) do imap.extract_responses(nil) { true } end
+ assert_raise(ArgumentError) do imap.extract_responses("OK") end
+ # matching nothing
+ assert_equal([172], imap.responses("EXISTS", &:dup))
+ assert_equal([], imap.extract_responses("EXISTS") { String === _1 })
+ assert_equal([172], imap.responses("EXISTS", &:dup))
+ # matching everything
+ assert_equal([172], imap.responses("EXISTS", &:dup))
+ assert_equal([172], imap.extract_responses("EXISTS", &:even?))
+ assert_equal([], imap.responses("EXISTS", &:dup))
+ # matching some
+ server.unsolicited("101 FETCH (UID 1111 FLAGS (\\Seen))")
+ server.unsolicited("102 FETCH (UID 2222 FLAGS (\\Seen \\Flagged))")
+ server.unsolicited("103 FETCH (UID 3333 FLAGS (\\Deleted))")
+ wait_for_response_count(imap, type: "FETCH", count: 3)
+
+ result = imap.extract_responses("FETCH") { _1.flags.include?(:Flagged) }
+ assert_equal(
+ [
+ Net::IMAP::FetchData.new(
+ 102, {"UID" => 2222, "FLAGS" => [:Seen, :Flagged]}
+ ),
+ ],
+ result,
+ )
+ assert_equal 2, imap.responses("FETCH", &:count)
+
+ result = imap.extract_responses("FETCH") { _1.flags.include?(:Deleted) }
+ assert_equal(
+ [Net::IMAP::FetchData.new(103, {"UID" => 3333, "FLAGS" => [:Deleted]})],
+ result
+ )
+ assert_equal 1, imap.responses("FETCH", &:count)
+ end
+ end
+
+end