imap-backup 5.0.0 → 6.0.0.rc2

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 (70) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +8 -7
  3. data/bin/imap-backup +4 -0
  4. data/docs/development.md +10 -4
  5. data/imap-backup.gemspec +2 -7
  6. data/lib/cli_coverage.rb +18 -0
  7. data/lib/imap/backup/account/connection.rb +7 -11
  8. data/lib/imap/backup/account/folder.rb +0 -16
  9. data/lib/imap/backup/account.rb +31 -11
  10. data/lib/imap/backup/cli/folders.rb +3 -3
  11. data/lib/imap/backup/cli/migrate.rb +3 -3
  12. data/lib/imap/backup/cli/restore.rb +20 -4
  13. data/lib/imap/backup/cli/utils.rb +2 -2
  14. data/lib/imap/backup/cli.rb +6 -7
  15. data/lib/imap/backup/configuration.rb +1 -11
  16. data/lib/imap/backup/downloader.rb +13 -9
  17. data/lib/imap/backup/serializer/directory.rb +37 -0
  18. data/lib/imap/backup/serializer/imap.rb +120 -0
  19. data/lib/imap/backup/serializer/mbox.rb +23 -94
  20. data/lib/imap/backup/serializer/mbox_enumerator.rb +2 -0
  21. data/lib/imap/backup/serializer.rb +180 -3
  22. data/lib/imap/backup/setup/account.rb +52 -29
  23. data/lib/imap/backup/setup/helpers.rb +1 -1
  24. data/lib/imap/backup/thunderbird/mailbox_exporter.rb +1 -1
  25. data/lib/imap/backup/version.rb +2 -2
  26. data/lib/imap/backup.rb +0 -1
  27. data/spec/features/backup_spec.rb +22 -29
  28. data/spec/features/restore_spec.rb +8 -6
  29. data/spec/features/support/aruba.rb +12 -3
  30. data/spec/features/support/backup_directory.rb +0 -4
  31. data/spec/features/support/email_server.rb +0 -1
  32. data/spec/spec_helper.rb +4 -9
  33. data/spec/unit/imap/backup/account/connection_spec.rb +36 -8
  34. data/spec/unit/imap/backup/account/folder_spec.rb +18 -16
  35. data/spec/unit/imap/backup/account_spec.rb +246 -0
  36. data/spec/unit/imap/backup/cli/accounts_spec.rb +12 -1
  37. data/spec/unit/imap/backup/cli/backup_spec.rb +19 -0
  38. data/spec/unit/imap/backup/cli/folders_spec.rb +39 -0
  39. data/spec/unit/imap/backup/cli/local_spec.rb +26 -7
  40. data/spec/unit/imap/backup/cli/migrate_spec.rb +80 -0
  41. data/spec/unit/imap/backup/cli/restore_spec.rb +67 -0
  42. data/spec/unit/imap/backup/cli/setup_spec.rb +17 -0
  43. data/spec/unit/imap/backup/cli/utils_spec.rb +68 -5
  44. data/spec/unit/imap/backup/cli_spec.rb +93 -0
  45. data/spec/unit/imap/backup/client/apple_mail_spec.rb +9 -0
  46. data/spec/unit/imap/backup/configuration_spec.rb +2 -2
  47. data/spec/unit/imap/backup/downloader_spec.rb +60 -8
  48. data/spec/unit/imap/backup/logger_spec.rb +1 -1
  49. data/spec/unit/imap/backup/migrator_spec.rb +1 -1
  50. data/spec/unit/imap/backup/sanitizer_spec.rb +42 -0
  51. data/spec/unit/imap/backup/serializer/directory_spec.rb +37 -0
  52. data/spec/unit/imap/backup/serializer/imap_spec.rb +218 -0
  53. data/spec/unit/imap/backup/serializer/mbox_spec.rb +62 -183
  54. data/spec/unit/imap/backup/serializer_spec.rb +296 -0
  55. data/spec/unit/imap/backup/setup/account_spec.rb +120 -25
  56. data/spec/unit/imap/backup/setup/helpers_spec.rb +15 -0
  57. data/spec/unit/imap/backup/thunderbird/mailbox_exporter_spec.rb +116 -0
  58. data/spec/unit/imap/backup/uploader_spec.rb +1 -1
  59. data/spec/unit/retry_on_error_spec.rb +34 -0
  60. metadata +44 -37
  61. data/lib/imap/backup/serializer/mbox_store.rb +0 -217
  62. data/lib/thunderbird/install.rb +0 -16
  63. data/lib/thunderbird/local_folder.rb +0 -65
  64. data/lib/thunderbird/profile.rb +0 -30
  65. data/lib/thunderbird/profiles.rb +0 -71
  66. data/lib/thunderbird/subdirectory.rb +0 -93
  67. data/lib/thunderbird/subdirectory_placeholder.rb +0 -21
  68. data/lib/thunderbird.rb +0 -14
  69. data/spec/gather_rspec_coverage.rb +0 -1
  70. data/spec/unit/imap/backup/serializer/mbox_store_spec.rb +0 -329
@@ -1,8 +1,9 @@
1
1
  require "imap/backup/cli/accounts"
2
2
 
3
3
  describe Imap::Backup::CLI::Accounts do
4
- subject { described_class.new }
4
+ subject { described_class.new(required_accounts) }
5
5
 
6
+ let(:required_accounts) { [] }
6
7
  let(:accounts) { [account1, account2] }
7
8
  let(:account1) do
8
9
  instance_double(
@@ -43,5 +44,15 @@ describe Imap::Backup::CLI::Accounts do
43
44
  end.to raise_error(Imap::Backup::ConfigurationNotFound, /not found/)
44
45
  end
45
46
  end
47
+
48
+ context "when an account list is provided" do
49
+ let(:required_accounts) { %w(a2@example.com) }
50
+
51
+ specify "calls the block with each account" do
52
+ result = subject.map { |a| a }
53
+
54
+ expect(result).to eq([account2])
55
+ end
56
+ end
46
57
  end
47
58
  end
@@ -0,0 +1,19 @@
1
+ module Imap::Backup
2
+ describe CLI::Backup do
3
+ subject { described_class.new({}) }
4
+
5
+ let(:connection) { instance_double(Account::Connection, run_backup: nil) }
6
+
7
+ before do
8
+ # rubocop:disable RSpec/SubjectStub
9
+ allow(subject).to receive(:each_connection).with([]).and_yield(connection)
10
+ # rubocop:enable RSpec/SubjectStub
11
+ end
12
+
13
+ it "runs the backup for each connection" do
14
+ subject.run
15
+
16
+ expect(connection).to have_received(:run_backup)
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,39 @@
1
+ module Imap::Backup
2
+ describe CLI::Folders do
3
+ subject { described_class.new({}) }
4
+
5
+ let(:connection) do
6
+ instance_double(
7
+ Account::Connection, account: account, folder_names: folder_names
8
+ )
9
+ end
10
+ let(:account) { instance_double(Account, username: "user") }
11
+ let(:folder_names) { ["my-folder"] }
12
+
13
+ before do
14
+ allow(Kernel).to receive(:puts)
15
+ allow(Kernel).to receive(:warn)
16
+ # rubocop:disable RSpec/SubjectStub
17
+ allow(subject).to receive(:each_connection).with([]).and_yield(connection)
18
+ # rubocop:enable RSpec/SubjectStub
19
+
20
+ subject.run
21
+ end
22
+
23
+ it "lists folders" do
24
+ expect(Kernel).to have_received(:puts).with("\tmy-folder")
25
+ end
26
+
27
+ context "when the folder list is not fetched" do
28
+ let(:folder_names) { nil }
29
+
30
+ it "warns" do
31
+ expect(Kernel).to have_received(:warn).with(/Unable to list/)
32
+ end
33
+
34
+ it "doesn't fail" do
35
+ subject.run
36
+ end
37
+ end
38
+ end
39
+ end
@@ -23,20 +23,22 @@ describe Imap::Backup::CLI::Local do
23
23
  let(:folder) { instance_double(Imap::Backup::Account::Folder, name: "bar") }
24
24
  let(:serializer) do
25
25
  instance_double(
26
- Imap::Backup::Serializer::Mbox,
26
+ Imap::Backup::Serializer,
27
27
  uids: uids,
28
- each_message: [[123, message]]
28
+ each_message: each_message
29
29
  )
30
30
  end
31
31
  let(:uids) { ["123"] }
32
+ let(:each_message) { [[123, message]] }
32
33
  let(:message) do
33
34
  instance_double(
34
35
  Email::Mboxrd::Message,
35
36
  date: Date.today,
36
- subject: "Ciao",
37
+ subject: message_subject,
37
38
  supplied_body: "Supplied"
38
39
  )
39
40
  end
41
+ let(:message_subject) { "Ciao" }
40
42
  let(:email) { "foo@example.com" }
41
43
 
42
44
  before do
@@ -64,18 +66,35 @@ describe Imap::Backup::CLI::Local do
64
66
  end
65
67
 
66
68
  describe "list" do
67
- it "lists downloaded emails" do
68
- subject.list(email, "bar")
69
+ before { subject.list(email, "bar") }
69
70
 
71
+ it "lists downloaded emails" do
70
72
  expect(Kernel).to have_received(:puts).with(/Ciao/)
71
73
  end
74
+
75
+ context "when the subject line is too long" do
76
+ let(:message_subject) { "A" * 70 }
77
+
78
+ it "is shortened" do
79
+ expect(Kernel).to have_received(:puts).with(/\sA{57}\.\.\./)
80
+ end
81
+ end
72
82
  end
73
83
 
74
84
  describe "show" do
75
- it "prints a downloaded email" do
76
- subject.show(email, "bar", "123")
85
+ before { subject.show(email, "bar", uids.join(",")) }
77
86
 
87
+ it "prints a downloaded email" do
78
88
  expect(Kernel).to have_received(:puts).with("Supplied")
79
89
  end
90
+
91
+ context "when more than one email is requested" do
92
+ let(:uids) { %w(123 456) }
93
+ let(:each_message) { [[123, message], [456, message]] }
94
+
95
+ it "prints a header" do
96
+ expect(Kernel).to have_received(:puts).with(/\| UID: 123 /)
97
+ end
98
+ end
80
99
  end
81
100
  end
@@ -0,0 +1,80 @@
1
+ module Imap::Backup
2
+ describe CLI::Migrate do
3
+ subject { described_class.new(source, destination, **options) }
4
+
5
+ let(:source) { "source" }
6
+ let(:destination) { "destination" }
7
+ let(:options) { {} }
8
+ let(:config) { instance_double(Configuration, accounts: [account1, account2]) }
9
+ let(:account1) { instance_double(Account, username: "source", local_path: "path") }
10
+ let(:account2) { instance_double(Account, username: "destination", connection: connection) }
11
+ let(:connection) { instance_double(Account::Connection) }
12
+ let(:migrator) { instance_double(Migrator, run: nil) }
13
+ let(:imap_pathname) { Pathname.new("path/foo.imap") }
14
+
15
+ before do
16
+ allow(Configuration).to receive(:new) { config }
17
+ allow(Pathname).to receive(:glob).and_yield(imap_pathname)
18
+ allow(Migrator).to receive(:new) { migrator }
19
+ allow(Account::Folder).to receive(:new).and_call_original
20
+ end
21
+
22
+ it "migrates each folder" do
23
+ subject.run
24
+
25
+ expect(migrator).to have_received(:run)
26
+ end
27
+
28
+ context "when source and destination emails are the same" do
29
+ let(:destination) { "source" }
30
+
31
+ it "fails" do
32
+ expect do
33
+ subject.run
34
+ end.to raise_error(RuntimeError, /cannot be the same/)
35
+ end
36
+ end
37
+
38
+ context "when the source account is not found" do
39
+ let(:source) { "unknown" }
40
+
41
+ it "fails" do
42
+ expect do
43
+ subject.run
44
+ end.to raise_error(RuntimeError, /does not exist/)
45
+ end
46
+ end
47
+
48
+ context "when the destination account is not found" do
49
+ let(:destination) { "unknown" }
50
+
51
+ it "fails" do
52
+ expect do
53
+ subject.run
54
+ end.to raise_error(RuntimeError, /does not exist/)
55
+ end
56
+ end
57
+
58
+ context "when source_prefix is supplied" do
59
+ let(:options) { {source_prefix: "src/"} }
60
+ let(:imap_pathname) { Pathname.new("path/src/foo.imap") }
61
+
62
+ it "removes the prefix" do
63
+ subject.run
64
+
65
+ expect(Account::Folder).to have_received(:new).with(anything, "foo")
66
+ end
67
+ end
68
+
69
+ context "when destination_prefix is supplied" do
70
+ let(:options) { {destination_prefix: "dest/"} }
71
+ let(:imap_pathname) { Pathname.new("path/foo.imap") }
72
+
73
+ it "removes the prefix" do
74
+ subject.run
75
+
76
+ expect(Account::Folder).to have_received(:new).with(anything, "dest/foo")
77
+ end
78
+ end
79
+ end
80
+ end
@@ -0,0 +1,67 @@
1
+ # rubocop:disable RSpec/SubjectStub
2
+ module Imap::Backup
3
+ describe CLI::Restore do
4
+ subject { described_class.new(email, options) }
5
+
6
+ let(:connection) { instance_double(Account::Connection, restore: nil) }
7
+
8
+ describe "#run" do
9
+ context "when an email is provided" do
10
+ let(:email) { "email" }
11
+ let(:options) { {} }
12
+
13
+ before do
14
+ allow(subject).to receive(:connection).with(email) { connection }
15
+
16
+ subject.run
17
+ end
18
+
19
+ it "runs restore on the account" do
20
+ expect(connection).to have_received(:restore)
21
+ end
22
+ end
23
+
24
+ context "when neither an email nor a list of account names is provided" do
25
+ let(:email) { nil }
26
+ let(:options) { {} }
27
+
28
+ before do
29
+ allow(subject).to receive(:each_connection).with([]).and_yield(connection)
30
+
31
+ subject.run
32
+ end
33
+
34
+ it "runs restore on each account" do
35
+ expect(connection).to have_received(:restore)
36
+ end
37
+ end
38
+
39
+ context "when an email and a list of account names is provided" do
40
+ let(:email) { "email" }
41
+ let(:options) { {accounts: "email2"} }
42
+
43
+ it "fails" do
44
+ expect do
45
+ subject.run
46
+ end.to raise_error(RuntimeError, /Pass either an email or the --accounts option/)
47
+ end
48
+ end
49
+
50
+ context "when just a list of account names is provided" do
51
+ let(:email) { nil }
52
+ let(:options) { {accounts: "email2"} }
53
+
54
+ before do
55
+ allow(subject).to receive(:each_connection).with(["email2"]).and_yield(connection)
56
+
57
+ subject.run
58
+ end
59
+
60
+ it "runs restore on each account" do
61
+ expect(connection).to have_received(:restore)
62
+ end
63
+ end
64
+ end
65
+ end
66
+ end
67
+ # rubocop:enable RSpec/SubjectStub
@@ -0,0 +1,17 @@
1
+ module Imap::Backup
2
+ describe CLI::Setup do
3
+ subject { described_class.new }
4
+
5
+ let(:setup) { instance_double(Setup, run: nil) }
6
+
7
+ before do
8
+ allow(Setup).to receive(:new) { setup }
9
+ end
10
+
11
+ it "reruns the setup process" do
12
+ subject.run
13
+
14
+ expect(setup).to have_received(:run)
15
+ end
16
+ end
17
+ end
@@ -11,7 +11,8 @@ module Imap::Backup
11
11
  instance_double(
12
12
  Account::Connection,
13
13
  account: account,
14
- backup_folders: [folder]
14
+ backup_folders: [folder],
15
+ local_folders: ["folder"]
15
16
  )
16
17
  end
17
18
  let(:account) do
@@ -31,18 +32,80 @@ module Imap::Backup
31
32
  end
32
33
  let(:serializer) do
33
34
  instance_double(
34
- Serializer::Mbox,
35
+ Serializer,
35
36
  uids: %w(123 789),
36
37
  apply_uid_validity: nil,
37
- save: nil
38
+ append: nil
38
39
  )
39
40
  end
41
+ let(:exporter) { instance_double(Thunderbird::MailboxExporter, run: nil) }
40
42
  let(:email) { "foo@example.com" }
41
43
 
42
44
  before do
43
45
  allow(CLI::Accounts).to receive(:new) { accounts }
44
46
  allow(Account::Connection).to receive(:new) { connection }
45
- allow(Serializer::Mbox).to receive(:new) { serializer }
47
+ allow(Serializer).to receive(:new) { serializer }
48
+ end
49
+
50
+ describe "#export_to_thunderbird" do
51
+ let(:command) { subject.export_to_thunderbird(email) }
52
+ let(:options) { {} }
53
+ let(:profiles) { instance_double(Thunderbird::Profiles, installs: installs, profile: named_profile) }
54
+ let(:installs) { [install1] }
55
+ let(:install1) { instance_double(Thunderbird::Install, default: default_install) }
56
+ let(:default_install) { "default" }
57
+ let(:named_profile) { "named" }
58
+
59
+ before do
60
+ allow(Thunderbird::MailboxExporter).to receive(:new) { exporter }
61
+ allow(Thunderbird::Profiles).to receive(:new) { profiles }
62
+ # rubocop:disable RSpec/SubjectStub
63
+ allow(subject).to receive(:options) { options }
64
+ # rubocop:enable RSpec/SubjectStub
65
+ end
66
+
67
+ context "when no profile_name is supplied" do
68
+ context "when no default Thunderbird profile is found" do
69
+ let(:default_install) { nil }
70
+
71
+ it "fails" do
72
+ expect do
73
+ command
74
+ end.to raise_error(RuntimeError, /Default .*? not found/)
75
+ end
76
+ end
77
+
78
+ context "when there is more than one install" do
79
+ let(:installs) { [install1, install2] }
80
+ let(:install2) { instance_double(Thunderbird::Install, default: default_install) }
81
+
82
+ it "fails" do
83
+ expect do
84
+ command
85
+ end.to raise_error(RuntimeError, /multiple installs.*?supply a profile name/m)
86
+ end
87
+ end
88
+ end
89
+
90
+ context "when a profile_name is supplied" do
91
+ let(:options) { {"profile" => "profile"} }
92
+
93
+ context "when the supplied profile_name is not found" do
94
+ let(:named_profile) { nil }
95
+
96
+ it "fails" do
97
+ expect do
98
+ command
99
+ end.to raise_error(RuntimeError, /profile 'profile' not found/)
100
+ end
101
+ end
102
+ end
103
+
104
+ it "exports the profile" do
105
+ command
106
+
107
+ expect(exporter).to have_received(:run)
108
+ end
46
109
  end
47
110
 
48
111
  describe "ignore_history" do
@@ -55,7 +118,7 @@ module Imap::Backup
55
118
  it "fills the local folder with fake emails" do
56
119
  subject.ignore_history(email)
57
120
 
58
- expect(serializer).to have_received(:save).with("456", /From: fake@email.com/)
121
+ expect(serializer).to have_received(:append).with("456", /From: fake@email.com/)
59
122
  end
60
123
  end
61
124
  end
@@ -0,0 +1,93 @@
1
+ module Imap::Backup
2
+ RSpec.describe CLI do
3
+ describe ".exit_on_failure?" do
4
+ it "is true" do
5
+ expect(described_class.exit_on_failure?).to be true
6
+ end
7
+ end
8
+
9
+ describe "#backup" do
10
+ let(:backup) { instance_double(CLI::Backup, run: nil) }
11
+
12
+ before do
13
+ allow(CLI::Backup).to receive(:new) { backup }
14
+
15
+ subject.backup
16
+ end
17
+
18
+ it "runs Backup" do
19
+ expect(backup).to have_received(:run)
20
+ end
21
+ end
22
+
23
+ describe "#folders" do
24
+ let(:folders) { instance_double(CLI::Folders, run: nil) }
25
+
26
+ before do
27
+ allow(CLI::Folders).to receive(:new) { folders }
28
+
29
+ subject.folders
30
+ end
31
+
32
+ it "runs folders" do
33
+ expect(folders).to have_received(:run)
34
+ end
35
+ end
36
+
37
+ describe "#migrate" do
38
+ let(:migrate) { instance_double(CLI::Migrate, run: nil) }
39
+
40
+ before do
41
+ allow(CLI::Migrate).to receive(:new) { migrate }
42
+
43
+ subject.migrate("source", "destination")
44
+ end
45
+
46
+ it "runs migrate" do
47
+ expect(migrate).to have_received(:run)
48
+ end
49
+ end
50
+
51
+ describe "#restore" do
52
+ let(:restore) { instance_double(CLI::Restore, run: nil) }
53
+
54
+ before do
55
+ allow(CLI::Restore).to receive(:new) { restore }
56
+
57
+ subject.restore
58
+ end
59
+
60
+ it "runs restore" do
61
+ expect(restore).to have_received(:run)
62
+ end
63
+ end
64
+
65
+ describe "#setup" do
66
+ let(:setup) { instance_double(CLI::Setup, run: nil) }
67
+
68
+ before do
69
+ allow(CLI::Setup).to receive(:new) { setup }
70
+
71
+ subject.setup
72
+ end
73
+
74
+ it "runs setup" do
75
+ expect(setup).to have_received(:run)
76
+ end
77
+ end
78
+
79
+ describe "#status" do
80
+ let(:status) { instance_double(CLI::Status, run: nil) }
81
+
82
+ before do
83
+ allow(CLI::Status).to receive(:new) { status }
84
+
85
+ subject.status
86
+ end
87
+
88
+ it "runs status" do
89
+ expect(status).to have_received(:run)
90
+ end
91
+ end
92
+ end
93
+ end
@@ -0,0 +1,9 @@
1
+ module Imap::Backup
2
+ RSpec.describe Client::AppleMail do
3
+ describe "#provider_root" do
4
+ it "is an empty string" do
5
+ expect(subject.provider_root).to eq("")
6
+ end
7
+ end
8
+ end
9
+ end
@@ -59,7 +59,7 @@ describe Imap::Backup::Configuration do
59
59
  end
60
60
 
61
61
  context "with accounts flagged 'delete'" do
62
- before { subject.accounts[0].mark_for_deletion! }
62
+ before { subject.accounts[0].mark_for_deletion }
63
63
 
64
64
  it "is true" do
65
65
  expect(subject.modified?).to be_truthy
@@ -177,7 +177,7 @@ describe Imap::Backup::Configuration do
177
177
  before do
178
178
  allow(subject.accounts[0]).to receive(:to_h) { "Account1" }
179
179
  allow(subject.accounts[1]).to receive(:to_h) { "Account2" }
180
- subject.accounts[0].mark_for_deletion!
180
+ subject.accounts[0].mark_for_deletion
181
181
  end
182
182
 
183
183
  it "does not save them" do
@@ -1,6 +1,6 @@
1
1
  describe Imap::Backup::Downloader do
2
2
  describe "#run" do
3
- subject { described_class.new(folder, serializer) }
3
+ subject { described_class.new(folder, serializer, **options) }
4
4
 
5
5
  let(:body) { "blah" }
6
6
  let(:folder) do
@@ -8,17 +8,19 @@ describe Imap::Backup::Downloader do
8
8
  Imap::Backup::Account::Folder,
9
9
  fetch_multi: [{uid: "111", body: body}],
10
10
  name: "folder",
11
- uids: folder_uids
11
+ uids: remote_uids
12
12
  )
13
13
  end
14
- let(:folder_uids) { %w(111 222 333) }
14
+ let(:remote_uids) { %w(111 222 333) }
15
15
  let(:serializer) do
16
- instance_double(Imap::Backup::Serializer::Mbox, save: nil, uids: ["222"])
16
+ instance_double(Imap::Backup::Serializer, append: nil, uids: local_uids)
17
17
  end
18
+ let(:local_uids) { ["222"] }
19
+ let(:options) { {} }
18
20
 
19
21
  context "with fetched messages" do
20
22
  specify "are saved" do
21
- expect(serializer).to receive(:save).with("111", body)
23
+ expect(serializer).to receive(:append).with("111", body)
22
24
 
23
25
  subject.run
24
26
  end
@@ -26,7 +28,7 @@ describe Imap::Backup::Downloader do
26
28
 
27
29
  context "with messages which are already present" do
28
30
  specify "are skipped" do
29
- expect(serializer).to_not receive(:save).with("222", anything)
31
+ expect(serializer).to_not receive(:append).with("222", anything)
30
32
 
31
33
  subject.run
32
34
  end
@@ -34,11 +36,61 @@ describe Imap::Backup::Downloader do
34
36
 
35
37
  context "with failed fetches" do
36
38
  specify "are skipped" do
37
- allow(folder).to receive(:fetch).with("333") { nil }
38
- expect(serializer).to_not receive(:save).with("333", anything)
39
+ allow(folder).to receive(:fetch_multi) { nil }
40
+ expect(serializer).to_not receive(:append)
39
41
 
40
42
  subject.run
41
43
  end
42
44
  end
45
+
46
+ context "when the block size is greater than one" do
47
+ let(:remote_uids) { %w(111 999) }
48
+ let(:local_uids) { [] }
49
+ let(:options) { {multi_fetch_size: 2} }
50
+
51
+ context "when the first fetch fails" do
52
+ before do
53
+ allow(folder).to receive(:fetch_multi).with(["111", "999"]) { nil }
54
+ allow(folder).to receive(:fetch_multi).with(["111"]).
55
+ and_return([{uid: "111", body: body}]).
56
+ and_return([{uid: "999", body: body}])
57
+
58
+ subject.run
59
+ end
60
+
61
+ it "retries fetching messages singly" do
62
+ expect(serializer).to have_received(:append).with("111", body)
63
+ expect(serializer).to have_received(:append).with("999", body)
64
+ end
65
+ end
66
+ end
67
+
68
+ context "when no body is returned by the fetch" do
69
+ let(:remote_uids) { %w(111) }
70
+
71
+ before do
72
+ allow(folder).to receive(:fetch_multi).with(["111"]) { [{uid: "111", body: nil}] }
73
+
74
+ subject.run
75
+ end
76
+
77
+ it "skips the append" do
78
+ expect(serializer).to_not have_received(:append)
79
+ end
80
+ end
81
+
82
+ context "when the UID is not returned by the fetch" do
83
+ let(:remote_uids) { %w(111) }
84
+
85
+ before do
86
+ allow(folder).to receive(:fetch_multi).with(["111"]) { [{uid: nil, body: body}] }
87
+
88
+ subject.run
89
+ end
90
+
91
+ it "skips the append" do
92
+ expect(serializer).to_not have_received(:append)
93
+ end
94
+ end
43
95
  end
44
96
  end
@@ -39,7 +39,7 @@ module Imap::Backup
39
39
  expect(described_class.logger.level).to eq(::Logger::Severity::ERROR)
40
40
  end
41
41
 
42
- it "sets the Net::IMAP debug flag" do
42
+ it "doesn't set the Net::IMAP debug flag" do
43
43
  expect(Net::IMAP.debug).to be_a(FalseClass)
44
44
  end
45
45
  end
@@ -4,7 +4,7 @@ module Imap::Backup
4
4
  RSpec.describe Migrator do
5
5
  subject { described_class.new(serializer, folder, reset: reset) }
6
6
 
7
- let(:serializer) { instance_double(Serializer::MboxStore, uids: [1]) }
7
+ let(:serializer) { instance_double(Serializer, uids: [1]) }
8
8
  let(:folder) do
9
9
  instance_double(
10
10
  Account::Folder,