imap-backup 5.2.0 → 6.0.0.rc2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (52) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +9 -2
  3. data/docs/development.md +10 -4
  4. data/lib/cli_coverage.rb +1 -1
  5. data/lib/imap/backup/account/connection.rb +7 -11
  6. data/lib/imap/backup/account.rb +31 -11
  7. data/lib/imap/backup/cli/folders.rb +3 -3
  8. data/lib/imap/backup/cli/migrate.rb +3 -3
  9. data/lib/imap/backup/cli/utils.rb +2 -2
  10. data/lib/imap/backup/configuration.rb +1 -11
  11. data/lib/imap/backup/downloader.rb +13 -9
  12. data/lib/imap/backup/serializer/directory.rb +37 -0
  13. data/lib/imap/backup/serializer/imap.rb +120 -0
  14. data/lib/imap/backup/serializer/mbox.rb +23 -94
  15. data/lib/imap/backup/serializer/mbox_enumerator.rb +2 -0
  16. data/lib/imap/backup/serializer.rb +180 -3
  17. data/lib/imap/backup/setup/account.rb +52 -29
  18. data/lib/imap/backup/setup/helpers.rb +1 -1
  19. data/lib/imap/backup/thunderbird/mailbox_exporter.rb +1 -1
  20. data/lib/imap/backup/version.rb +3 -3
  21. data/lib/imap/backup.rb +0 -1
  22. data/spec/features/backup_spec.rb +8 -16
  23. data/spec/features/support/aruba.rb +4 -3
  24. data/spec/unit/imap/backup/account/connection_spec.rb +36 -8
  25. data/spec/unit/imap/backup/account/folder_spec.rb +10 -0
  26. data/spec/unit/imap/backup/account_spec.rb +246 -0
  27. data/spec/unit/imap/backup/cli/accounts_spec.rb +12 -1
  28. data/spec/unit/imap/backup/cli/backup_spec.rb +19 -0
  29. data/spec/unit/imap/backup/cli/folders_spec.rb +39 -0
  30. data/spec/unit/imap/backup/cli/local_spec.rb +26 -7
  31. data/spec/unit/imap/backup/cli/migrate_spec.rb +80 -0
  32. data/spec/unit/imap/backup/cli/restore_spec.rb +67 -0
  33. data/spec/unit/imap/backup/cli/setup_spec.rb +17 -0
  34. data/spec/unit/imap/backup/cli/utils_spec.rb +68 -5
  35. data/spec/unit/imap/backup/cli_spec.rb +93 -0
  36. data/spec/unit/imap/backup/client/apple_mail_spec.rb +9 -0
  37. data/spec/unit/imap/backup/configuration_spec.rb +2 -2
  38. data/spec/unit/imap/backup/downloader_spec.rb +59 -7
  39. data/spec/unit/imap/backup/migrator_spec.rb +1 -1
  40. data/spec/unit/imap/backup/sanitizer_spec.rb +42 -0
  41. data/spec/unit/imap/backup/serializer/directory_spec.rb +37 -0
  42. data/spec/unit/imap/backup/serializer/imap_spec.rb +218 -0
  43. data/spec/unit/imap/backup/serializer/mbox_spec.rb +62 -183
  44. data/spec/unit/imap/backup/serializer_spec.rb +296 -0
  45. data/spec/unit/imap/backup/setup/account_spec.rb +120 -25
  46. data/spec/unit/imap/backup/setup/helpers_spec.rb +15 -0
  47. data/spec/unit/imap/backup/thunderbird/mailbox_exporter_spec.rb +116 -0
  48. data/spec/unit/imap/backup/uploader_spec.rb +1 -1
  49. data/spec/unit/retry_on_error_spec.rb +34 -0
  50. metadata +36 -7
  51. data/lib/imap/backup/serializer/mbox_store.rb +0 -217
  52. data/spec/unit/imap/backup/serializer/mbox_store_spec.rb +0 -329
@@ -1,115 +1,44 @@
1
- require "forwardable"
2
-
3
- require "imap/backup/serializer/mbox_store"
4
-
5
1
  module Imap::Backup
6
2
  class Serializer::Mbox
7
- extend Forwardable
8
- def_delegators :store, :mbox_pathname
3
+ attr_reader :folder_path
9
4
 
10
- attr_reader :path
11
- attr_reader :folder
12
-
13
- def initialize(path, folder)
14
- @path = path
15
- @folder = folder
5
+ def initialize(folder_path)
6
+ @folder_path = folder_path
16
7
  end
17
8
 
18
- def apply_uid_validity(value)
19
- case
20
- when store.uid_validity.nil?
21
- store.uid_validity = value
22
- nil
23
- when store.uid_validity == value
24
- # NOOP
25
- nil
26
- else
27
- apply_new_uid_validity value
9
+ def append(message)
10
+ File.open(pathname, "ab") do |file|
11
+ file.write message
28
12
  end
29
13
  end
30
14
 
31
- def force_uid_validity(value)
32
- store.uid_validity = value
15
+ def exist?
16
+ File.exist?(pathname)
33
17
  end
34
18
 
35
- def uids
36
- store.uids
37
- end
38
-
39
- def load(uid)
40
- store.load(uid)
41
- end
42
-
43
- def each_message(uids)
44
- store.each_message(uids)
45
- end
46
-
47
- def save(uid, message)
48
- store.add(uid, message)
49
- end
50
-
51
- def rename(new_name)
52
- @folder = new_name
53
- store.rename new_name
54
- end
19
+ def length
20
+ return nil if !exist?
55
21
 
56
- def update_uid(old, new)
57
- store.update_uid old, new
22
+ File.stat(pathname).size
58
23
  end
59
24
 
60
- private
61
-
62
- def store
63
- @store ||=
64
- begin
65
- create_containing_directory
66
- Serializer::MboxStore.new(path, folder)
67
- end
25
+ def pathname
26
+ "#{folder_path}.mbox"
68
27
  end
69
28
 
70
- def apply_new_uid_validity(value)
71
- digit = 0
72
- new_name = nil
73
- loop do
74
- extra = digit.zero? ? "" : "-#{digit}"
75
- new_name = "#{folder}-#{store.uid_validity}#{extra}"
76
- test_store = Serializer::MboxStore.new(path, new_name)
77
- break if !test_store.exist?
78
-
79
- digit += 1
29
+ def rename(new_path)
30
+ if exist?
31
+ old_pathname = pathname
32
+ @folder_path = new_path
33
+ File.rename(old_pathname, pathname)
34
+ else
35
+ @folder_path = new_path
80
36
  end
81
- rename_store new_name, value
82
- end
83
-
84
- def rename_store(new_name, value)
85
- store.rename new_name
86
- @store = nil
87
- store.uid_validity = value
88
- new_name
89
- end
90
-
91
- def relative_path
92
- File.dirname(folder)
93
37
  end
94
38
 
95
- def containing_directory
96
- File.join(path, relative_path)
97
- end
98
-
99
- def full_path
100
- File.expand_path(containing_directory)
101
- end
102
-
103
- def create_containing_directory
104
- if !File.directory?(full_path)
105
- Utils.make_folder(
106
- path, relative_path, Serializer::DIRECTORY_PERMISSIONS
107
- )
108
- end
109
-
110
- if Utils.mode(full_path) !=
111
- Serializer::DIRECTORY_PERMISSIONS
112
- FileUtils.chmod Serializer::DIRECTORY_PERMISSIONS, full_path
39
+ def rewind(length)
40
+ File.open(pathname, File::RDWR | File::CREAT, 0o644) do |f|
41
+ f.truncate(length)
113
42
  end
114
43
  end
115
44
  end
@@ -1,4 +1,6 @@
1
1
  module Imap::Backup
2
+ class Serializer; end
3
+
2
4
  class Serializer::MboxEnumerator
3
5
  attr_reader :mbox_pathname
4
6
 
@@ -1,6 +1,183 @@
1
+ require "forwardable"
2
+
3
+ require "email/mboxrd/message"
4
+ require "imap/backup/serializer/imap"
5
+ require "imap/backup/serializer/mbox"
6
+ require "imap/backup/serializer/mbox_enumerator"
7
+
1
8
  module Imap::Backup
2
- module Serializer
3
- DIRECTORY_PERMISSIONS = 0o700
4
- FILE_PERMISSIONS = 0o600
9
+ class Serializer
10
+ extend Forwardable
11
+
12
+ def_delegator :mbox, :pathname, :mbox_pathname
13
+ def_delegators :imap, :uid_validity, :uids, :update_uid
14
+
15
+ attr_reader :folder
16
+ attr_reader :path
17
+
18
+ def initialize(path, folder)
19
+ @path = path
20
+ @folder = folder
21
+ end
22
+
23
+ def apply_uid_validity(value)
24
+ case
25
+ when uid_validity.nil?
26
+ imap.uid_validity = value
27
+ nil
28
+ when uid_validity == value
29
+ # NOOP
30
+ nil
31
+ else
32
+ apply_new_uid_validity value
33
+ end
34
+ end
35
+
36
+ def force_uid_validity(value)
37
+ imap.uid_validity = value
38
+ end
39
+
40
+ def append(uid, message)
41
+ raise "Can't add messages without uid_validity" if !imap.uid_validity
42
+
43
+ uid = uid.to_i
44
+ if imap.include?(uid)
45
+ Logger.logger.debug(
46
+ "[#{folder}] message #{uid} already downloaded - skipping"
47
+ )
48
+ return
49
+ end
50
+
51
+ do_append uid, message
52
+ end
53
+
54
+ def load(uid_maybe_string)
55
+ uid = uid_maybe_string.to_i
56
+ message_index = imap.index(uid)
57
+ return nil if message_index.nil?
58
+
59
+ load_nth(message_index)
60
+ end
61
+
62
+ def load_nth(index)
63
+ enumerator = Serializer::MboxEnumerator.new(mbox.pathname)
64
+ enumerator.each.with_index do |raw, i|
65
+ next if i != index
66
+
67
+ return Email::Mboxrd::Message.from_serialized(raw)
68
+ end
69
+ nil
70
+ end
71
+
72
+ def each_message(required_uids)
73
+ return enum_for(:each_message, required_uids) if !block_given?
74
+
75
+ indexes = required_uids.each.with_object({}) do |uid_maybe_string, acc|
76
+ uid = uid_maybe_string.to_i
77
+ index = imap.index(uid)
78
+ acc[index] = uid if index
79
+ end
80
+ enumerator = Serializer::MboxEnumerator.new(mbox.pathname)
81
+ enumerator.each.with_index do |raw, i|
82
+ uid = indexes[i]
83
+ next if !uid
84
+
85
+ yield uid, Email::Mboxrd::Message.from_serialized(raw)
86
+ end
87
+ end
88
+
89
+ def rename(new_name)
90
+ # Initialize so we get memoized instances with the correct folder_path
91
+ mbox
92
+ imap
93
+ @folder = new_name
94
+ ensure_containing_directory
95
+ mbox.rename folder_path
96
+ imap.rename folder_path
97
+ end
98
+
99
+ private
100
+
101
+ def do_append(uid, message)
102
+ mboxrd_message = Email::Mboxrd::Message.new(message)
103
+ initial = mbox.length || 0
104
+ mbox_appended = false
105
+ begin
106
+ mbox.append mboxrd_message.to_serialized
107
+ mbox_appended = true
108
+ imap.append uid
109
+ rescue StandardError => e
110
+ mbox.rewind(initial) if mbox_appended
111
+
112
+ message = <<-ERROR.gsub(/^\s*/m, "")
113
+ [#{folder}] failed to append message #{uid}:
114
+ #{message}. #{e}:
115
+ #{e.backtrace.join("\n")}"
116
+ ERROR
117
+ Logger.logger.warn message
118
+ end
119
+ end
120
+
121
+ def mbox
122
+ @mbox ||=
123
+ begin
124
+ ensure_containing_directory
125
+ Serializer::Mbox.new(folder_path)
126
+ end
127
+ end
128
+
129
+ def imap
130
+ @imap ||=
131
+ begin
132
+ ensure_containing_directory
133
+ Serializer::Imap.new(folder_path)
134
+ end
135
+ end
136
+
137
+ def folder_path
138
+ folder_path_for(path, folder)
139
+ end
140
+
141
+ def folder_path_for(path, folder)
142
+ relative = File.join(path, folder)
143
+ File.expand_path(relative)
144
+ end
145
+
146
+ def ensure_containing_directory
147
+ relative = File.dirname(folder)
148
+ directory = Serializer::Directory.new(path, relative)
149
+ directory.ensure_exists
150
+ end
151
+
152
+ def apply_new_uid_validity(value)
153
+ new_name = rename_existing_folder
154
+ # Clear memoization so we get empty data
155
+ @mbox = nil
156
+ @imap = nil
157
+ imap.uid_validity = value
158
+
159
+ new_name
160
+ end
161
+
162
+ def rename_existing_folder
163
+ digit = 0
164
+ new_name = nil
165
+ loop do
166
+ extra = digit.zero? ? "" : "-#{digit}"
167
+ new_name = "#{folder}-#{imap.uid_validity}#{extra}"
168
+ new_folder_path = folder_path_for(path, new_name)
169
+ test_mbox = Serializer::Mbox.new(new_folder_path)
170
+ test_imap = Serializer::Imap.new(new_folder_path)
171
+ break if !test_mbox.exist? && !test_imap.exist?
172
+
173
+ digit += 1
174
+ end
175
+
176
+ previous = folder
177
+ rename(new_name)
178
+ @folder = previous
179
+
180
+ new_name
181
+ end
5
182
  end
6
183
  end
@@ -26,6 +26,7 @@ module Imap::Backup
26
26
  modify_password menu
27
27
  modify_backup_path menu
28
28
  choose_folders menu
29
+ modify_multi_fetch_size menu
29
30
  modify_server menu
30
31
  modify_connection_options menu
31
32
  test_connection menu
@@ -37,21 +38,30 @@ module Imap::Backup
37
38
 
38
39
  def header(menu)
39
40
  modified = account.modified? ? "*" : ""
40
- connection_options =
41
- if account.connection_options
42
- escaped =
43
- JSON.generate(account.connection_options).
44
- gsub('"', '\"')
45
- "\n connection options #{escaped}"
46
- end
41
+
42
+ if account.multi_fetch_size > 1
43
+ multi_fetch_size = "\nmulti-fetch #{account.multi_fetch_size}"
44
+ end
45
+
46
+ if account.connection_options
47
+ escaped =
48
+ JSON.generate(account.connection_options)
49
+ connection_options =
50
+ "\nconnection options '#{escaped}'"
51
+ space = " " * 12
52
+ else
53
+ connection_options = nil
54
+ space = " " * 4
55
+ end
56
+
47
57
  menu.header = <<~HEADER.chomp
48
58
  #{helpers.title_prefix} Account#{modified}
49
59
 
50
- email #{account.username}
51
- password #{masked_password}
52
- path #{account.local_path}
53
- folders #{folders.map { |f| f[:name] }.join(', ')}
54
- server #{account.server}#{connection_options}
60
+ email #{space}#{account.username}
61
+ password#{space}#{masked_password}
62
+ path #{space}#{account.local_path}
63
+ folders #{space}#{folders.map { |f| f[:name] }.join(', ')}#{multi_fetch_size}
64
+ server #{space}#{account.server}#{connection_options}
55
65
 
56
66
  Choose an action
57
67
  HEADER
@@ -70,12 +80,10 @@ module Imap::Backup
70
80
  )
71
81
  else
72
82
  account.username = username
73
- # rubocop:disable Style/IfUnlessModifier
74
83
  default = default_server(username)
75
84
  if default && (account.server.nil? || (account.server == ""))
76
85
  account.server = default
77
86
  end
78
- # rubocop:enable Style/IfUnlessModifier
79
87
  end
80
88
  end
81
89
  end
@@ -88,20 +96,6 @@ module Imap::Backup
88
96
  end
89
97
  end
90
98
 
91
- def modify_server(menu)
92
- menu.choice("modify server") do
93
- server = highline.ask("server: ")
94
- account.server = server if !server.nil?
95
- end
96
- end
97
-
98
- def modify_connection_options(menu)
99
- menu.choice("modify connection options") do
100
- connection_options = highline.ask("connections options (as JSON): ")
101
- account.connection_options = connection_options if !connection_options.nil?
102
- end
103
- end
104
-
105
99
  def path_modification_validator(path)
106
100
  same = config.accounts.find do |a|
107
101
  a.username != account.username && a.local_path == path
@@ -130,6 +124,35 @@ module Imap::Backup
130
124
  end
131
125
  end
132
126
 
127
+ def modify_multi_fetch_size(menu)
128
+ menu.choice("modify multi-fetch size (number of emails to fetch at a time)") do
129
+ size = highline.ask("size: ")
130
+ int = size.to_i
131
+ account.multi_fetch_size = int if int.positive?
132
+ end
133
+ end
134
+
135
+ def modify_server(menu)
136
+ menu.choice("modify server") do
137
+ server = highline.ask("server: ")
138
+ account.server = server if !server.nil?
139
+ end
140
+ end
141
+
142
+ def modify_connection_options(menu)
143
+ menu.choice("modify connection options") do
144
+ connection_options = highline.ask("connections options (as JSON): ")
145
+ if !connection_options.nil?
146
+ begin
147
+ account.connection_options = connection_options
148
+ rescue JSON::ParserError
149
+ Kernel.puts "Malformed JSON, please try again"
150
+ highline.ask "Press a key "
151
+ end
152
+ end
153
+ end
154
+ end
155
+
133
156
  def test_connection(menu)
134
157
  menu.choice("test connection") do
135
158
  result = Setup::ConnectionTester.new(account).test
@@ -141,7 +164,7 @@ module Imap::Backup
141
164
  def delete_account(menu)
142
165
  menu.choice("delete") do
143
166
  if highline.agree("Are you sure? (y/n) ")
144
- account.mark_for_deletion!
167
+ account.mark_for_deletion
145
168
  throw :done
146
169
  end
147
170
  end
@@ -9,7 +9,7 @@ module Imap::Backup
9
9
  end
10
10
 
11
11
  def version
12
- Version::VERSION
12
+ VERSION
13
13
  end
14
14
  end
15
15
  end
@@ -19,7 +19,7 @@ module Imap::Backup
19
19
 
20
20
  def run
21
21
  local_folder_ok = local_folder.set_up
22
- return if !local_folder_ok
22
+ return false if !local_folder_ok
23
23
 
24
24
  if local_folder.msf_exists?
25
25
  if force
@@ -1,9 +1,9 @@
1
1
  module Imap; end
2
2
 
3
3
  module Imap::Backup
4
- MAJOR = 5
5
- MINOR = 2
4
+ MAJOR = 6
5
+ MINOR = 0
6
6
  REVISION = 0
7
- PRE = nil
7
+ PRE = "rc2".freeze
8
8
  VERSION = [MAJOR, MINOR, REVISION, PRE].compact.map(&:to_s).join(".")
9
9
  end
data/lib/imap/backup.rb CHANGED
@@ -8,7 +8,6 @@ require "imap/backup/downloader"
8
8
  require "imap/backup/logger"
9
9
  require "imap/backup/uploader"
10
10
  require "imap/backup/serializer"
11
- require "imap/backup/serializer/mbox"
12
11
  require "imap/backup/setup"
13
12
  require "imap/backup/setup/account"
14
13
  require "imap/backup/setup/asker"
@@ -70,10 +70,18 @@ RSpec.describe "backup", type: :aruba, docker: true do
70
70
  expect(mbox_content(renamed_folder)).to eq(message_as_mbox_entry(msg3))
71
71
  end
72
72
 
73
+ it "renames the old metadata file" do
74
+ expect(imap_parsed(renamed_folder)).to be_a Hash
75
+ end
76
+
73
77
  it "downloads messages" do
74
78
  expect(mbox_content(folder)).to eq(messages_as_mbox)
75
79
  end
76
80
 
81
+ it "creates a metadata file" do
82
+ expect(imap_parsed(folder)).to be_a Hash
83
+ end
84
+
77
85
  context "when a renamed local backup exists" do
78
86
  let!(:pre) do
79
87
  super()
@@ -88,21 +96,5 @@ RSpec.describe "backup", type: :aruba, docker: true do
88
96
  end
89
97
  end
90
98
  end
91
-
92
- context "when an unversioned .imap file is found" do
93
- let!(:pre) do
94
- create_directory local_backup_path
95
- File.open(imap_path(folder), "w") { |f| f.write "old format imap" }
96
- File.open(mbox_path(folder), "w") { |f| f.write "old format emails" }
97
- end
98
-
99
- it "replaces the .imap file with a versioned JSON file" do
100
- expect(imap_metadata[:uids]).to eq(folder_uids)
101
- end
102
-
103
- it "does the download" do
104
- expect(mbox_content(folder)).to eq(messages_as_mbox)
105
- end
106
- end
107
99
  end
108
100
  end
@@ -1,6 +1,7 @@
1
1
  require "aruba/rspec"
2
2
 
3
3
  require_relative "backup_directory"
4
+ require "imap/backup/serializer/mbox"
4
5
 
5
6
  Aruba.configure do |config|
6
7
  config.home_directory = File.expand_path("./tmp/home")
@@ -36,10 +37,10 @@ module StoreHelpers
36
37
  account = config.accounts.find { |a| a.username == email }
37
38
  raise "Account not found" if !account
38
39
  FileUtils.mkdir_p account.local_path
39
- store = Imap::Backup::Serializer::MboxStore.new(account.local_path, folder)
40
- store.uid_validity = "42" if !store.uid_validity
40
+ serializer = Imap::Backup::Serializer.new(account.local_path, folder)
41
+ serializer.force_uid_validity("42") if !serializer.uid_validity
41
42
  serialized = to_serialized(from: from, subject: subject, body: body)
42
- store.add(uid, serialized)
43
+ serializer.append uid, serialized
43
44
  end
44
45
 
45
46
  def to_serialized(from:, subject:, body:)
@@ -24,21 +24,24 @@ describe Imap::Backup::Account::Connection do
24
24
  let(:account) do
25
25
  instance_double(
26
26
  Imap::Backup::Account,
27
- username: USERNAME,
27
+ username: username,
28
28
  password: PASSWORD,
29
29
  local_path: LOCAL_PATH,
30
30
  folders: config_folders,
31
+ multi_fetch_size: multi_fetch_size,
31
32
  server: server,
32
33
  connection_options: nil
33
34
  )
34
35
  end
36
+ let(:username) { USERNAME }
35
37
  let(:config_folders) { [FOLDER_CONFIG] }
38
+ let(:multi_fetch_size) { 1 }
36
39
  let(:root_info) do
37
40
  instance_double(Net::IMAP::MailboxList, name: ROOT_NAME)
38
41
  end
39
42
  let(:serializer) do
40
43
  instance_double(
41
- Imap::Backup::Serializer::Mbox,
44
+ Imap::Backup::Serializer,
42
45
  folder: serialized_folder,
43
46
  force_uid_validity: nil,
44
47
  apply_uid_validity: new_uid_validity,
@@ -87,6 +90,23 @@ describe Imap::Backup::Account::Connection do
87
90
  end
88
91
  end
89
92
 
93
+ context "when the provider is Apple" do
94
+ let(:username) { "user@mac.com" }
95
+ let(:apple_client) do
96
+ instance_double(
97
+ Imap::Backup::Client::AppleMail, login: nil
98
+ )
99
+ end
100
+
101
+ before do
102
+ allow(Imap::Backup::Client::AppleMail).to receive(:new) { apple_client }
103
+ end
104
+
105
+ it "returns the Apple client" do
106
+ expect(result).to eq(apple_client)
107
+ end
108
+ end
109
+
90
110
  context "when run" do
91
111
  before { subject.client }
92
112
 
@@ -116,7 +136,7 @@ describe Imap::Backup::Account::Connection do
116
136
 
117
137
  before do
118
138
  allow(Imap::Backup::Account::Folder).to receive(:new) { folder }
119
- allow(Imap::Backup::Serializer::Mbox).to receive(:new) { serializer }
139
+ allow(Imap::Backup::Serializer).to receive(:new) { serializer }
120
140
  end
121
141
 
122
142
  it "creates the path" do
@@ -150,16 +170,24 @@ describe Imap::Backup::Account::Connection do
150
170
  let(:exists) { true }
151
171
  let(:uid_validity) { 123 }
152
172
  let(:downloader) { instance_double(Imap::Backup::Downloader, run: nil) }
173
+ let(:multi_fetch_size) { 10 }
153
174
 
154
175
  before do
155
176
  allow(Imap::Backup::Downloader).
156
177
  to receive(:new).with(folder, serializer, anything) { downloader }
157
178
  allow(Imap::Backup::Account::Folder).to receive(:new).
158
179
  with(subject, BACKUP_FOLDER) { folder }
159
- allow(Imap::Backup::Serializer::Mbox).to receive(:new).
180
+ allow(Imap::Backup::Serializer).to receive(:new).
160
181
  with(LOCAL_PATH, IMAP_FOLDER) { serializer }
161
182
  end
162
183
 
184
+ it "passes the multi_fetch_size" do
185
+ subject.run_backup
186
+
187
+ expect(Imap::Backup::Downloader).to have_received(:new).
188
+ with(anything, anything, {multi_fetch_size: 10})
189
+ end
190
+
163
191
  context "with supplied config_folders" do
164
192
  it "runs the downloader" do
165
193
  expect(downloader).to receive(:run)
@@ -184,7 +212,7 @@ describe Imap::Backup::Account::Connection do
184
212
  before do
185
213
  allow(Imap::Backup::Account::Folder).to receive(:new).
186
214
  with(subject, ROOT_NAME) { folder }
187
- allow(Imap::Backup::Serializer::Mbox).to receive(:new).
215
+ allow(Imap::Backup::Serializer).to receive(:new).
188
216
  with(LOCAL_PATH, ROOT_NAME) { serializer }
189
217
  end
190
218
 
@@ -273,18 +301,18 @@ describe Imap::Backup::Account::Connection do
273
301
  end
274
302
  let(:updated_serializer) do
275
303
  instance_double(
276
- Imap::Backup::Serializer::Mbox, force_uid_validity: nil
304
+ Imap::Backup::Serializer, force_uid_validity: nil
277
305
  )
278
306
  end
279
307
 
280
308
  before do
281
309
  allow(Imap::Backup::Account::Folder).to receive(:new).
282
310
  with(subject, FOLDER_NAME) { folder }
283
- allow(Imap::Backup::Serializer::Mbox).to receive(:new).
311
+ allow(Imap::Backup::Serializer).to receive(:new).
284
312
  with(anything, FOLDER_NAME) { serializer }
285
313
  allow(Imap::Backup::Account::Folder).to receive(:new).
286
314
  with(subject, "new name") { updated_folder }
287
- allow(Imap::Backup::Serializer::Mbox).to receive(:new).
315
+ allow(Imap::Backup::Serializer).to receive(:new).
288
316
  with(anything, "new name") { updated_serializer }
289
317
  allow(Imap::Backup::Uploader).to receive(:new).
290
318
  with(folder, serializer) { uploader }
@@ -64,6 +64,16 @@ describe Imap::Backup::Account::Folder do
64
64
  expect(subject.uids).to eq([])
65
65
  end
66
66
  end
67
+
68
+ context "when the UID search fails" do
69
+ before do
70
+ allow(client).to receive(:uid_search).and_raise(NoMethodError)
71
+ end
72
+
73
+ it "returns an empty array" do
74
+ expect(subject.uids).to eq([])
75
+ end
76
+ end
67
77
  end
68
78
 
69
79
  describe "#fetch_multi" do