imap-backup 4.0.2 → 4.0.3

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 (44) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +9 -1
  3. data/docs/development.md +1 -2
  4. data/imap-backup.gemspec +1 -0
  5. data/lib/email/provider/apple_mail.rb +7 -0
  6. data/lib/email/provider/default.rb +12 -0
  7. data/lib/email/provider/fastmail.rb +7 -0
  8. data/lib/email/provider/gmail.rb +7 -0
  9. data/lib/email/provider.rb +16 -26
  10. data/lib/imap/backup/account/connection.rb +19 -29
  11. data/lib/imap/backup/account/folder.rb +9 -10
  12. data/lib/imap/backup/cli/helpers.rb +1 -0
  13. data/lib/imap/backup/cli/local.rb +4 -1
  14. data/lib/imap/backup/cli/utils.rb +16 -5
  15. data/lib/imap/backup/client/apple_mail.rb +11 -0
  16. data/lib/imap/backup/client/default.rb +51 -0
  17. data/lib/imap/backup/configuration/account.rb +3 -1
  18. data/lib/imap/backup/configuration/connection_tester.rb +1 -1
  19. data/lib/imap/backup/configuration/store.rb +10 -5
  20. data/lib/imap/backup/downloader.rb +3 -4
  21. data/lib/imap/backup/thunderbird/mailbox_exporter.rb +12 -25
  22. data/lib/imap/backup/version.rb +1 -1
  23. data/lib/thunderbird/install.rb +16 -0
  24. data/lib/thunderbird/local_folder.rb +33 -65
  25. data/lib/thunderbird/profiles.rb +18 -10
  26. data/lib/thunderbird/subdirectory.rb +96 -0
  27. data/lib/thunderbird/{local_folder_placeholder.rb → subdirectory_placeholder.rb} +4 -4
  28. data/lib/thunderbird.rb +10 -2
  29. data/spec/features/restore_spec.rb +1 -1
  30. data/spec/features/support/email_server.rb +2 -2
  31. data/spec/unit/email/provider/apple_mail_spec.rb +7 -0
  32. data/spec/unit/email/provider/default_spec.rb +17 -0
  33. data/spec/unit/email/provider/fastmail_spec.rb +7 -0
  34. data/spec/unit/email/provider/gmail_spec.rb +7 -0
  35. data/spec/unit/email/provider_spec.rb +12 -25
  36. data/spec/unit/imap/backup/account/connection_spec.rb +26 -51
  37. data/spec/unit/imap/backup/account/folder_spec.rb +22 -22
  38. data/spec/unit/imap/backup/cli/utils_spec.rb +2 -2
  39. data/spec/unit/imap/backup/client/default_spec.rb +22 -0
  40. data/spec/unit/imap/backup/configuration/connection_tester_spec.rb +3 -3
  41. data/spec/unit/imap/backup/configuration/store_spec.rb +25 -12
  42. data/spec/unit/imap/backup/downloader_spec.rb +1 -2
  43. metadata +57 -26
  44. data/lib/thunderbird/mailbox.rb +0 -25
@@ -1,97 +1,65 @@
1
1
  require "thunderbird/profile"
2
- require "thunderbird/local_folder_placeholder"
2
+ require "thunderbird/subdirectory"
3
3
 
4
+ # A local folder is a file containing emails
4
5
  class Thunderbird::LocalFolder
5
- attr_reader :folder_path
6
+ attr_reader :path
6
7
  attr_reader :profile
7
8
 
8
- def initialize(profile, folder_path)
9
+ def initialize(profile, path)
9
10
  @profile = profile
10
- @folder_path = folder_path
11
+ @path = path
11
12
  end
12
13
 
13
- def local_folder_placeholder
14
- if parent
15
- path = File.join(parent.full_path, folder_path_elements[-1])
16
- Thunderbird::LocalFolderPlaceholder.new(path)
17
- end
18
- end
14
+ def set_up
15
+ return if path_elements.empty?
19
16
 
20
- def directory_is_directory?
21
- File.directory?(full_path)
22
- end
17
+ return true if !in_subdirectory?
23
18
 
24
- def full_path
25
- File.join(profile.local_folders_path, relative_path)
19
+ subdirectory.set_up
26
20
  end
27
21
 
28
- def parent
29
- if folder_path_elements.count > 0
30
- self.class.new(profile, File.join(folder_path_elements[0..-2]))
22
+ def full_path
23
+ if in_subdirectory?
24
+ File.join(subdirectory.full_path, folder_name)
25
+ else
26
+ folder_name
31
27
  end
32
28
  end
33
29
 
34
- def folder_path_elements
35
- folder_path.split(File::SEPARATOR)
30
+ def exists?
31
+ File.exist?(full_path)
36
32
  end
37
33
 
38
- def directory_exists?
39
- File.exists?(full_path)
34
+ def msf_path
35
+ "#{path}.msf"
40
36
  end
41
37
 
42
- def is_directory?
43
- File.directory?(full_path)
38
+ def msf_exists?
39
+ File.exist?(msf_path)
44
40
  end
45
41
 
46
- def subdirectories
47
- folder_path_elements.map { |p| "#{p}.sbd" }
48
- end
42
+ private
49
43
 
50
- def relative_path
51
- File.join(subdirectories)
44
+ def in_subdirectory?
45
+ path_elements.count > 1
52
46
  end
53
47
 
54
- def set_up
55
- ok = check
56
- return if !ok
48
+ def subdirectory
49
+ return nil if !in_subdirectory?
57
50
 
58
- ensure_initialized
51
+ Thunderbird::Subdirectory.new(profile, subdirectory_path)
59
52
  end
60
53
 
61
- private
62
-
63
- def ensure_initialized
64
- return true if !parent
65
-
66
- parent.ensure_initialized
67
-
68
- local_folder_placeholder.ensure_initialized
54
+ def path_elements
55
+ path.split(File::SEPARATOR)
56
+ end
69
57
 
70
- FileUtils.mkdir_p full_path
58
+ def subdirectory_path
59
+ File.join(path_elements[0..-2])
71
60
  end
72
61
 
73
- def check
74
- return true if !parent
75
-
76
- parent_ok = parent.check
77
-
78
- return if !parent_ok
79
-
80
- case
81
- when local_folder_placeholder.exists? && !directory_exists?
82
- Kernel.puts "Can't set up folder '#{folder_path}': '#{local_folder_placeholder.path}' exists, but '#{full_path}' is missing"
83
- false
84
- when directory_exists? && !local_folder_placeholder.exists?
85
- Kernel.puts "Can't set up folder '#{folder_path}': '#{full_path}' exists, but '#{local_folder_placeholder.path}' is missing"
86
- false
87
- when local_folder_placeholder.exists? && !local_folder_placeholder.is_regular?
88
- Kernel.puts "Can't set up folder '#{folder_path}': '#{local_folder_placeholder.path}' exists, but it is not a regular file"
89
- false
90
- when directory_exists? && !is_directory?
91
- Kernel.puts "Can't set up folder '#{folder_path}': '#{full_path}' exists, but it is not a directory"
92
- false
93
- else
94
- true
95
- end
62
+ def folder_name
63
+ path_elements[-1]
96
64
  end
97
65
  end
@@ -1,10 +1,11 @@
1
1
  require "thunderbird"
2
+ require "thunderbird/install"
2
3
  require "thunderbird/profile"
3
4
 
4
5
  # http://kb.mozillazine.org/Profiles.ini_file
5
6
  class Thunderbird::Profiles
6
- def default
7
- title, entries = blocks.find { |_name, entries| entries[:Default] == "1" }
7
+ def profile_for_path(path)
8
+ title, entries = blocks.find { |_name, entries| entries[:Path] == path }
8
9
 
9
10
  Thunderbird::Profile.new(title, entries) if title
10
11
  end
@@ -12,11 +13,16 @@ class Thunderbird::Profiles
12
13
  def profile(name)
13
14
  title, entries = blocks.find { |_name, entries| entries[:Name] == name }
14
15
 
15
- return nil if !title
16
-
17
16
  Thunderbird::Profile.new(title, entries) if title
18
17
  end
19
18
 
19
+ def installs
20
+ @installs ||= begin
21
+ pairs = blocks.filter { |name, _entries| name.start_with?("Install") }
22
+ pairs.map { |title, entries| Thunderbird::Install.new(title, entries) }
23
+ end
24
+ end
25
+
20
26
  private
21
27
 
22
28
  # Parse profiles.ini.
@@ -31,7 +37,11 @@ class Thunderbird::Profiles
31
37
 
32
38
  loop do
33
39
  line = f.gets
34
- break if !line
40
+ if !line
41
+ blocks[title] = entries if title
42
+ break
43
+ end
44
+
35
45
  line.chomp!
36
46
 
37
47
  # Is this line the start of a new block
@@ -43,12 +53,10 @@ class Thunderbird::Profiles
43
53
  # Start a new block
44
54
  title = match[1]
45
55
  entries = {}
46
- else
56
+ elsif line != ""
47
57
  # Collect entries until we get to the next title
48
- if line != ""
49
- key, value = line.split("=")
50
- entries[key.to_sym] = value
51
- end
58
+ key, value = line.split("=")
59
+ entries[key.to_sym] = value
52
60
  end
53
61
  end
54
62
  end
@@ -0,0 +1,96 @@
1
+ require "thunderbird/subdirectory_placeholder"
2
+
3
+ class Thunderbird::Subdirectory
4
+ # `path` is the UI path, it doesn't have the '.sbd' extensions
5
+ # that are present in the real, file system path
6
+ attr_reader :path
7
+ attr_reader :profile
8
+
9
+ def initialize(profile, path)
10
+ @profile = profile
11
+ @path = path
12
+ end
13
+
14
+ def set_up
15
+ raise "Cannot create a subdirectory without a path" if !sub_directory?
16
+
17
+ if sub_sub_directory?
18
+ parent_ok = parent.set_up
19
+ return false if !parent_ok
20
+ end
21
+
22
+ ok = check
23
+ return false if !ok
24
+
25
+ FileUtils.mkdir_p full_path
26
+ placeholder.touch
27
+
28
+ true
29
+ end
30
+
31
+ # subdirectory relative path is 'Foo.sbd/Bar.sbd/Baz.sbd'
32
+ def full_path
33
+ relative_path = File.join(subdirectories)
34
+ File.join(profile.local_folders_path, relative_path)
35
+ end
36
+
37
+ private
38
+
39
+ def sub_directory?
40
+ path_elements.any?
41
+ end
42
+
43
+ def sub_sub_directory?
44
+ path_elements.count > 1
45
+ end
46
+
47
+ def parent
48
+ return nil if !sub_sub_directory?
49
+
50
+ self.class.new(profile, File.join(path_elements[0..-2]))
51
+ end
52
+
53
+ # placeholder relative path is 'Foo.sbd/Bar.sbd/Baz'
54
+ def placeholder
55
+ @placeholder = begin
56
+ relative_path = File.join(subdirectories[0..-2], path_elements[-1])
57
+ path = File.join(profile.local_folders_path, relative_path)
58
+ Thunderbird::SubdirectoryPlaceholder.new(path)
59
+ end
60
+ end
61
+
62
+ def path_elements
63
+ path.split(File::SEPARATOR)
64
+ end
65
+
66
+ def exists?
67
+ File.exist?(full_path)
68
+ end
69
+
70
+ def directory?
71
+ File.directory?(full_path)
72
+ end
73
+
74
+ def subdirectories
75
+ path_elements.map { |p| "#{p}.sbd" }
76
+ end
77
+
78
+ def check
79
+ case
80
+ when placeholder.exists? && !exists?
81
+ Kernel.puts "Can't set up folder '#{folder_path}': '#{placeholder.path}' exists, but '#{full_path}' is missing"
82
+ false
83
+ when exists? && !placeholder.exists?
84
+ Kernel.puts "Can't set up folder '#{folder_path}': '#{full_path}' exists, but '#{placeholder.path}' is missing"
85
+ false
86
+ when placeholder.exists? && !placeholder.regular?
87
+ Kernel.puts "Can't set up folder '#{folder_path}': '#{placeholder.path}' exists, but it is not a regular file"
88
+ false
89
+ when exists? && !directory?
90
+ Kernel.puts "Can't set up folder '#{folder_path}': '#{full_path}' exists, but it is not a directory"
91
+ false
92
+ else
93
+ true
94
+ end
95
+ end
96
+ end
@@ -1,6 +1,6 @@
1
1
  # Each subdirectory is "accompanied" by a blank
2
2
  # file of the same name (without the '.sbd' extension)
3
- class Thunderbird::LocalFolderPlaceholder
3
+ class Thunderbird::SubdirectoryPlaceholder
4
4
  attr_reader :path
5
5
 
6
6
  def initialize(path)
@@ -8,14 +8,14 @@ class Thunderbird::LocalFolderPlaceholder
8
8
  end
9
9
 
10
10
  def exists?
11
- File.exists?(path)
11
+ File.exist?(path)
12
12
  end
13
13
 
14
- def is_regular?
14
+ def regular?
15
15
  File.file?(path)
16
16
  end
17
17
 
18
- def ensure_initialized
18
+ def touch
19
19
  FileUtils.touch path
20
20
  end
21
21
  end
data/lib/thunderbird.rb CHANGED
@@ -1,6 +1,14 @@
1
+ require "os"
2
+
1
3
  class Thunderbird
2
4
  def data_path
3
- # TODO: Handle other OSes
4
- File.join(Dir.home, ".thunderbird")
5
+ case
6
+ when OS.linux?
7
+ File.join(Dir.home, ".thunderbird")
8
+ when OS.mac?
9
+ File.join(Dir.home, "Library", "Thunderbird")
10
+ when OS.windows?
11
+ File.join(ENV["APPDATA"].gsub("\\", "/"), "Thunderbird")
12
+ end
5
13
  end
6
14
  end
@@ -152,7 +152,7 @@ RSpec.describe "restore", type: :feature, docker: true do
152
152
  it "maintains encodings" do
153
153
  message =
154
154
  server_messages(folder).
155
- first["RFC822"]
155
+ first["BODY[]"]
156
156
 
157
157
  expect(message).to eq(server_message)
158
158
  end
@@ -1,5 +1,5 @@
1
1
  module EmailServerHelpers
2
- REQUESTED_ATTRIBUTES = %w(RFC822 FLAGS INTERNALDATE).freeze
2
+ REQUESTED_ATTRIBUTES = ["BODY[]"].freeze
3
3
  DEFAULT_EMAIL = "address@example.org".freeze
4
4
 
5
5
  def send_email(folder, options)
@@ -28,7 +28,7 @@ module EmailServerHelpers
28
28
  end
29
29
 
30
30
  def server_message_to_body(message)
31
- message["RFC822"]
31
+ message["BODY[]"]
32
32
  end
33
33
 
34
34
  def server_fetch_email(folder, uid)
@@ -0,0 +1,7 @@
1
+ describe Email::Provider::AppleMail do
2
+ describe "#host" do
3
+ it "returns host" do
4
+ expect(subject.host).to eq("imap.mail.me.com")
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,17 @@
1
+ describe Email::Provider::Default do
2
+ describe "#host" do
3
+ it "is unset" do
4
+ expect(subject.host).to be_nil
5
+ end
6
+ end
7
+
8
+ describe "#options" do
9
+ it "returns options" do
10
+ expect(subject.options).to be_a(Hash)
11
+ end
12
+
13
+ it "forces TLSv1_2" do
14
+ expect(subject.options[:ssl][:ssl_version]).to eq(:TLSv1_2)
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,7 @@
1
+ describe Email::Provider::Fastmail do
2
+ describe "#host" do
3
+ it "returns host" do
4
+ expect(subject.host).to eq("imap.fastmail.com")
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,7 @@
1
+ describe Email::Provider::GMail do
2
+ describe "#host" do
3
+ it "returns host" do
4
+ expect(subject.host).to eq("imap.gmail.com")
5
+ end
6
+ end
7
+ end
@@ -1,40 +1,27 @@
1
1
  describe Email::Provider do
2
- subject { described_class.new(:gmail) }
3
-
4
2
  describe ".for_address" do
5
3
  context "with known providers" do
6
4
  [
7
- ["gmail.com", :gmail],
8
- ["fastmail.fm", :fastmail]
9
- ].each do |domain, provider|
10
- it "recognizes #{provider}" do
5
+ ["fastmail.com", "Fastmail .com", Email::Provider::Fastmail],
6
+ ["fastmail.fm", "Fastmail .fm", Email::Provider::Fastmail],
7
+ ["gmail.com", "GMail", Email::Provider::GMail],
8
+ ["icloud.com", "Apple Mail icloud.com", Email::Provider::AppleMail],
9
+ ["mac.com", "Apple Mail mac.com", Email::Provider::AppleMail],
10
+ ["me.com", "Apple Mail me.com", Email::Provider::AppleMail]
11
+ ].each do |domain, name, klass|
12
+ it "recognizes #{name} addresses" do
11
13
  address = "foo@#{domain}"
12
- expect(described_class.for_address(address).provider).to eq(provider)
14
+ expect(described_class.for_address(address)).to be_a(klass)
13
15
  end
14
16
  end
15
17
  end
16
18
 
17
19
  context "with unknown providers" do
18
20
  it "returns a default provider" do
19
- result = described_class.for_address("foo@unknown.com").provider
20
- expect(result).to eq(:default)
21
- end
22
- end
23
- end
24
-
25
- describe "#options" do
26
- it "returns options" do
27
- expect(subject.options).to be_a(Hash)
28
- end
21
+ result = described_class.for_address("foo@unknown.com")
29
22
 
30
- it "forces TLSv1_2" do
31
- expect(subject.options[:ssl][:ssl_version]).to eq(:TLSv1_2)
32
- end
33
- end
34
-
35
- describe "#host" do
36
- it "returns host" do
37
- expect(subject.host).to eq("imap.gmail.com")
23
+ expect(result).to be_a(Email::Provider::Default)
24
+ end
38
25
  end
39
26
  end
40
27
  end
@@ -14,8 +14,10 @@ describe Imap::Backup::Account::Connection do
14
14
 
15
15
  subject { described_class.new(options) }
16
16
 
17
- let(:imap) do
18
- instance_double(Net::IMAP, authenticate: nil, login: nil, disconnect: nil)
17
+ let(:client) do
18
+ instance_double(
19
+ Imap::Backup::Client::Default, authenticate: nil, login: nil, disconnect: nil
20
+ )
19
21
  end
20
22
  let(:imap_folders) { [] }
21
23
  let(:options) do
@@ -45,15 +47,14 @@ describe Imap::Backup::Account::Connection do
45
47
  let(:new_uid_validity) { nil }
46
48
 
47
49
  before do
48
- allow(Net::IMAP).to receive(:new) { imap }
49
- allow(imap).to receive(:list).with("", "") { [root_info] }
50
- allow(imap).to receive(:list).with(ROOT_NAME, "*") { imap_folders }
50
+ allow(Imap::Backup::Client::Default).to receive(:new) { client }
51
+ allow(client).to receive(:list) { imap_folders }
51
52
  allow(Imap::Backup::Utils).to receive(:make_folder)
52
53
  end
53
54
 
54
55
  shared_examples "connects to IMAP" do
55
56
  it "logs in to the imap server" do
56
- expect(imap).to have_received(:login)
57
+ expect(client).to have_received(:login)
57
58
  end
58
59
  end
59
60
 
@@ -76,48 +77,34 @@ describe Imap::Backup::Account::Connection do
76
77
  end
77
78
  end
78
79
 
79
- describe "#imap" do
80
- let(:result) { subject.imap }
80
+ describe "#client" do
81
+ let(:result) { subject.client }
81
82
 
82
83
  it "returns the IMAP connection" do
83
- expect(result).to eq(imap)
84
+ expect(result).to eq(client)
84
85
  end
85
86
 
86
87
  it "uses the password" do
87
88
  result
88
89
 
89
- expect(imap).to have_received(:login).with(USERNAME, PASSWORD)
90
- end
91
-
92
- context "with the GMail IMAP server" do
93
- let(:server) { GMAIL_IMAP_SERVER }
94
- let(:refresh_token) { true }
95
- let(:result) { nil }
96
-
97
- context "when the password is not our copy of a GMail refresh token" do
98
- it "uses the password" do
99
- subject.imap
100
-
101
- expect(imap).to have_received(:login).with(USERNAME, PASSWORD)
102
- end
103
- end
90
+ expect(client).to have_received(:login).with(USERNAME, PASSWORD)
104
91
  end
105
92
 
106
93
  context "when the first login attempt fails" do
107
94
  before do
108
95
  outcomes = [-> { raise EOFError }, -> { true }]
109
- allow(imap).to receive(:login) { outcomes.shift.call }
96
+ allow(client).to receive(:login) { outcomes.shift.call }
110
97
  end
111
98
 
112
99
  it "retries" do
113
- subject.imap
100
+ subject.client
114
101
 
115
- expect(imap).to have_received(:login).twice
102
+ expect(client).to have_received(:login).twice
116
103
  end
117
104
  end
118
105
 
119
106
  context "when run" do
120
- before { subject.imap }
107
+ before { subject.client }
121
108
 
122
109
  include_examples "connects to IMAP"
123
110
  end
@@ -125,22 +112,12 @@ describe Imap::Backup::Account::Connection do
125
112
 
126
113
  describe "#folders" do
127
114
  let(:imap_folders) do
128
- [instance_double(Net::IMAP::MailboxList, name: BACKUP_FOLDER)]
115
+ [BACKUP_FOLDER]
129
116
  end
130
117
 
131
118
  it "returns the list of folders" do
132
119
  expect(subject.folders).to eq([BACKUP_FOLDER])
133
120
  end
134
-
135
- context "with non-ASCII folder names" do
136
- let(:imap_folders) do
137
- [instance_double(Net::IMAP::MailboxList, name: "Gel&APY-scht")]
138
- end
139
-
140
- it "converts them to UTF-8" do
141
- expect(subject.folders).to eq(["Gelöscht"])
142
- end
143
- end
144
121
  end
145
122
 
146
123
  describe "#status" do
@@ -208,9 +185,7 @@ describe Imap::Backup::Account::Connection do
208
185
  end
209
186
 
210
187
  context "without supplied config_folders" do
211
- let(:imap_folders) do
212
- [instance_double(Net::IMAP::MailboxList, name: ROOT_NAME)]
213
- end
188
+ let(:imap_folders) { [ROOT_NAME] }
214
189
 
215
190
  before do
216
191
  allow(Imap::Backup::Account::Folder).to receive(:new).
@@ -241,7 +216,7 @@ describe Imap::Backup::Account::Connection do
241
216
 
242
217
  context "when the imap server doesn't return folders" do
243
218
  let(:config_folders) { nil }
244
- let(:imap_folders) { nil }
219
+ let(:imap_folders) { [] }
245
220
 
246
221
  it "fails" do
247
222
  expect do
@@ -389,10 +364,10 @@ describe Imap::Backup::Account::Connection do
389
364
 
390
365
  describe "#reconnect" do
391
366
  context "when the IMAP connection has been used" do
392
- before { subject.imap }
367
+ before { subject.client }
393
368
 
394
369
  it "disconnects from the server" do
395
- expect(imap).to receive(:disconnect)
370
+ expect(client).to receive(:disconnect)
396
371
 
397
372
  subject.reconnect
398
373
  end
@@ -400,26 +375,26 @@ describe Imap::Backup::Account::Connection do
400
375
 
401
376
  context "when the IMAP connection has not been used" do
402
377
  it "does not disconnect from the server" do
403
- expect(imap).to_not receive(:disconnect)
378
+ expect(client).to_not receive(:disconnect)
404
379
 
405
380
  subject.reconnect
406
381
  end
407
382
  end
408
383
 
409
384
  it "causes reconnection on future access" do
410
- expect(Net::IMAP).to receive(:new)
385
+ expect(Imap::Backup::Client::Default).to receive(:new)
411
386
 
412
387
  subject.reconnect
413
- subject.imap
388
+ subject.client
414
389
  end
415
390
  end
416
391
 
417
392
  describe "#disconnect" do
418
393
  context "when the IMAP connection has been used" do
419
394
  it "disconnects from the server" do
420
- subject.imap
395
+ subject.client
421
396
 
422
- expect(imap).to receive(:disconnect)
397
+ expect(client).to receive(:disconnect)
423
398
 
424
399
  subject.disconnect
425
400
  end
@@ -427,7 +402,7 @@ describe Imap::Backup::Account::Connection do
427
402
 
428
403
  context "when the IMAP connection has not been used" do
429
404
  it "does not disconnect from the server" do
430
- expect(imap).to_not receive(:disconnect)
405
+ expect(client).to_not receive(:disconnect)
431
406
 
432
407
  subject.disconnect
433
408
  end