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
@@ -0,0 +1,246 @@
1
+ module Imap::Backup
2
+ describe Account do
3
+ subject { described_class.new(options) }
4
+
5
+ let(:options) { {username: "user", password: "pwd"} }
6
+
7
+ describe "#changes" do
8
+ it "lists changes" do
9
+ subject.username = "new"
10
+
11
+ expect(subject.changes).to eq(username: {from: "user", to: "new"})
12
+ end
13
+
14
+ context "when more than one change is made" do
15
+ it "retains the last one" do
16
+ subject.username = "first"
17
+ subject.username = "second"
18
+
19
+ expect(subject.changes).to eq(username: {from: "user", to: "second"})
20
+ end
21
+ end
22
+
23
+ context "when a value is reset to its original value" do
24
+ it "removes the change" do
25
+ subject.username = "changed"
26
+ subject.username = "user"
27
+
28
+ expect(subject.changes).to eq({})
29
+ end
30
+ end
31
+ end
32
+
33
+ describe "#connection" do
34
+ it "returns a Connection for the Account" do
35
+ result = subject.connection
36
+
37
+ expect(result).to be_a(Account::Connection)
38
+ expect(result.account).to be subject
39
+ end
40
+ end
41
+
42
+ describe "#valid?" do
43
+ context "with username and password" do
44
+ it "is true" do
45
+ expect(subject.valid?).to be true
46
+ end
47
+ end
48
+
49
+ context "without a username" do
50
+ let(:options) { {password: "pwd"} }
51
+
52
+ it "is false" do
53
+ expect(subject.valid?).to be false
54
+ end
55
+ end
56
+
57
+ context "without a password" do
58
+ let(:options) { {username: "user"} }
59
+
60
+ it "is false" do
61
+ expect(subject.valid?).to be false
62
+ end
63
+ end
64
+ end
65
+
66
+ describe "#modified?" do
67
+ context "with changes" do
68
+ it "is true" do
69
+ subject.username = "new"
70
+
71
+ expect(subject.modified?).to be true
72
+ end
73
+ end
74
+
75
+ context "without changes" do
76
+ it "is false" do
77
+ expect(subject.modified?).to be false
78
+ end
79
+ end
80
+ end
81
+
82
+ describe "#clear_changes" do
83
+ it "clears changes" do
84
+ subject.username = "new"
85
+ subject.clear_changes
86
+
87
+ expect(subject.changes).to eq({})
88
+ end
89
+ end
90
+
91
+ describe "#mark_for_deletion" do
92
+ it "sets marked_for_deletion" do
93
+ subject.mark_for_deletion
94
+
95
+ expect(subject.marked_for_deletion?).to be true
96
+ end
97
+ end
98
+
99
+ describe "#marked_for_deletion?" do
100
+ it "defaults to false" do
101
+ expect(subject.marked_for_deletion?).to be false
102
+ end
103
+ end
104
+
105
+ describe "#to_h" do
106
+ it "returns a Hash representation" do
107
+ expect(subject.to_h).to eq({username: "user", password: "pwd"})
108
+ end
109
+
110
+ context "when local_path is set" do
111
+ let(:options) { {username: "user", password: "pwd", local_path: "local_path"} }
112
+
113
+ it "includes local_path" do
114
+ expect(subject.to_h).to include({local_path: "local_path"})
115
+ end
116
+ end
117
+
118
+ context "when folders is set" do
119
+ let(:options) { {username: "user", password: "pwd", folders: ["folder"]} }
120
+
121
+ it "includes folders" do
122
+ expect(subject.to_h).to include({folders: ["folder"]})
123
+ end
124
+ end
125
+
126
+ context "when multi_fetch_size is set" do
127
+ let(:options) { {username: "user", password: "pwd", multi_fetch_size: "3"} }
128
+
129
+ it "includes multi_fetch_size" do
130
+ expect(subject.to_h).to include({multi_fetch_size: 3})
131
+ end
132
+ end
133
+
134
+ context "when server is set" do
135
+ let(:options) { {username: "user", password: "pwd", server: "server"} }
136
+
137
+ it "includes server" do
138
+ expect(subject.to_h).to include({server: "server"})
139
+ end
140
+ end
141
+
142
+ context "when connection_options is set" do
143
+ let(:options) { {username: "user", password: "pwd", connection_options: "foo"} }
144
+
145
+ it "includes connection_options" do
146
+ expect(subject.to_h).to include({connection_options: "foo"})
147
+ end
148
+ end
149
+ end
150
+
151
+ describe "#multi_fetch_size" do
152
+ let(:options) { {username: "user", password: "pwd", multi_fetch_size: multi_fetch_size} }
153
+ let(:multi_fetch_size) { 10 }
154
+
155
+ it "returns the initialized value" do
156
+ expect(subject.multi_fetch_size).to eq(10)
157
+ end
158
+
159
+ context "when the initialized value is a string representation of a positive number" do
160
+ let(:multi_fetch_size) { "10" }
161
+
162
+ it "returns one" do
163
+ expect(subject.multi_fetch_size).to eq(10)
164
+ end
165
+ end
166
+
167
+ context "when the initialized value is not a number" do
168
+ let(:multi_fetch_size) { "ciao" }
169
+
170
+ it "returns one" do
171
+ expect(subject.multi_fetch_size).to eq(1)
172
+ end
173
+ end
174
+
175
+ context "when the initialized value is not a positive number" do
176
+ let(:multi_fetch_size) { "-99" }
177
+
178
+ it "returns one" do
179
+ expect(subject.multi_fetch_size).to eq(1)
180
+ end
181
+ end
182
+ end
183
+
184
+ [
185
+ [:username, "username", "username"],
186
+ [:password, "password", "password"],
187
+ [:local_path, "local_path", "local_path"],
188
+ [:multi_fetch_size, "2", 2],
189
+ [:server, "server", "server"],
190
+ [:folders, ["folder"], ["folder"]],
191
+ [:connection_options, '{"some": "option"}', {"some" => "option"}]
192
+ ].each do |attribute, value, expected|
193
+ describe "##{attribute}=" do
194
+ let(:options) { {} }
195
+
196
+ before { subject.send(:"#{attribute}=", value) }
197
+
198
+ it "sets the #{attribute}" do
199
+ expect(subject.send(attribute)).to eq(expected)
200
+ end
201
+
202
+ it "adds a change" do
203
+ expect(subject.changes).to eq(attribute => {from: nil, to: expected})
204
+ end
205
+
206
+ if attribute == :folders
207
+ context "when the supplied value is not an Array" do
208
+ it "fails" do
209
+ expect do
210
+ subject.folders = "aaa"
211
+ end.to raise_error(RuntimeError, /must be an Array/)
212
+ end
213
+ end
214
+ end
215
+
216
+ if attribute == :multi_fetch_size
217
+ context "when the supplied value is not a number" do
218
+ before { subject.multi_fetch_size = "ciao" }
219
+
220
+ it "sets multi_fetch_size to one" do
221
+ expect(subject.multi_fetch_size).to eq(1)
222
+ end
223
+ end
224
+
225
+ context "when the supplied value is not a positive number" do
226
+ before { subject.multi_fetch_size = "-1" }
227
+
228
+ it "sets multi_fetch_size to one" do
229
+ expect(subject.multi_fetch_size).to eq(1)
230
+ end
231
+ end
232
+ end
233
+
234
+ if attribute == :connection_options
235
+ context "when the supplied value is not valid JSON" do
236
+ it "fails" do
237
+ expect do
238
+ subject.connection_options = "NOT JSON"
239
+ end.to raise_error(JSON::ParserError)
240
+ end
241
+ end
242
+ end
243
+ end
244
+ end
245
+ end
246
+ end
@@ -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