imap-backup 2.0.0 → 2.2.2

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 (56) hide show
  1. checksums.yaml +4 -4
  2. data/.rspec +0 -1
  3. data/.rspec-all +2 -0
  4. data/.rubocop.yml +15 -2
  5. data/.rubocop_todo.yml +58 -0
  6. data/.travis.yml +15 -2
  7. data/README.md +14 -22
  8. data/Rakefile +6 -3
  9. data/bin/imap-backup +5 -11
  10. data/imap-backup.gemspec +10 -6
  11. data/lib/email/mboxrd/message.rb +16 -16
  12. data/lib/imap/backup/account/connection.rb +38 -22
  13. data/lib/imap/backup/account/folder.rb +23 -7
  14. data/lib/imap/backup/configuration/account.rb +25 -21
  15. data/lib/imap/backup/configuration/asker.rb +3 -2
  16. data/lib/imap/backup/configuration/connection_tester.rb +1 -1
  17. data/lib/imap/backup/configuration/folder_chooser.rb +32 -5
  18. data/lib/imap/backup/configuration/list.rb +2 -0
  19. data/lib/imap/backup/configuration/setup.rb +2 -1
  20. data/lib/imap/backup/configuration/store.rb +3 -6
  21. data/lib/imap/backup/downloader.rb +8 -7
  22. data/lib/imap/backup/serializer/mbox.rb +44 -25
  23. data/lib/imap/backup/serializer/mbox_enumerator.rb +31 -0
  24. data/lib/imap/backup/serializer/mbox_store.rb +35 -32
  25. data/lib/imap/backup/uploader.rb +11 -2
  26. data/lib/imap/backup/utils.rb +11 -9
  27. data/lib/imap/backup/version.rb +2 -2
  28. data/spec/features/backup_spec.rb +6 -5
  29. data/spec/features/helper.rb +1 -1
  30. data/spec/features/restore_spec.rb +75 -27
  31. data/spec/features/support/backup_directory.rb +7 -7
  32. data/spec/features/support/email_server.rb +15 -11
  33. data/spec/features/support/shared/connection_context.rb +2 -2
  34. data/spec/features/support/shared/message_fixtures.rb +8 -0
  35. data/spec/spec_helper.rb +1 -1
  36. data/spec/support/fixtures.rb +2 -2
  37. data/spec/support/higline_test_helpers.rb +1 -1
  38. data/spec/unit/email/mboxrd/message_spec.rb +73 -53
  39. data/spec/unit/email/provider_spec.rb +3 -5
  40. data/spec/unit/imap/backup/account/connection_spec.rb +82 -59
  41. data/spec/unit/imap/backup/account/folder_spec.rb +75 -37
  42. data/spec/unit/imap/backup/configuration/account_spec.rb +95 -61
  43. data/spec/unit/imap/backup/configuration/asker_spec.rb +43 -45
  44. data/spec/unit/imap/backup/configuration/connection_tester_spec.rb +21 -22
  45. data/spec/unit/imap/backup/configuration/folder_chooser_spec.rb +66 -33
  46. data/spec/unit/imap/backup/configuration/list_spec.rb +32 -11
  47. data/spec/unit/imap/backup/configuration/setup_spec.rb +97 -56
  48. data/spec/unit/imap/backup/configuration/store_spec.rb +30 -25
  49. data/spec/unit/imap/backup/downloader_spec.rb +28 -26
  50. data/spec/unit/imap/backup/serializer/mbox_enumerator_spec.rb +45 -0
  51. data/spec/unit/imap/backup/serializer/mbox_spec.rb +109 -51
  52. data/spec/unit/imap/backup/serializer/mbox_store_spec.rb +232 -20
  53. data/spec/unit/imap/backup/uploader_spec.rb +23 -9
  54. data/spec/unit/imap/backup/utils_spec.rb +14 -15
  55. data/spec/unit/imap/backup_spec.rb +28 -0
  56. metadata +13 -7
@@ -1,6 +1,8 @@
1
- require "spec_helper"
1
+ # rubocop:disable RSpec/PredicateMatcher
2
2
 
3
3
  describe Imap::Backup::Account::Folder do
4
+ subject { described_class.new(connection, "my_folder") }
5
+
4
6
  let(:imap) do
5
7
  instance_double(
6
8
  Net::IMAP,
@@ -10,25 +12,23 @@ describe Imap::Backup::Account::Folder do
10
12
  responses: responses
11
13
  )
12
14
  end
13
- let(:connection) { double("Imap::Backup::Account::Connection", imap: imap) }
14
- let(:missing_mailbox_data) do
15
- double("Data", text: "Unknown Mailbox: my_folder")
15
+ let(:connection) do
16
+ instance_double(Imap::Backup::Account::Connection, imap: imap)
16
17
  end
17
- let(:missing_mailbox_response) do
18
- double("Response", data: missing_mailbox_data)
18
+ let(:missing_mailbox_data) do
19
+ OpenStruct.new(text: "Unknown Mailbox: my_folder")
19
20
  end
21
+ let(:missing_mailbox_response) { OpenStruct.new(data: missing_mailbox_data) }
20
22
  let(:missing_mailbox_error) do
21
23
  Net::IMAP::NoResponseError.new(missing_mailbox_response)
22
24
  end
23
25
  let(:responses) { [] }
24
26
  let(:append_response) { nil }
25
27
 
26
- subject { described_class.new(connection, "my_folder") }
27
-
28
- context "#uids" do
28
+ describe "#uids" do
29
29
  let(:uids) { [5678, 123] }
30
30
 
31
- before { allow(imap).to receive(:uid_search).and_return(uids) }
31
+ before { allow(imap).to receive(:uid_search) { uids } }
32
32
 
33
33
  it "lists available messages" do
34
34
  expect(subject.uids).to eq(uids.reverse)
@@ -43,10 +43,24 @@ describe Imap::Backup::Account::Folder do
43
43
  expect(subject.uids).to eq([])
44
44
  end
45
45
  end
46
+
47
+ context "with no SEARCH response in Net::IMAP" do
48
+ let(:no_method_error) do
49
+ NoMethodError.new("Somethimes SEARCH responses come out undefined")
50
+ end
51
+
52
+ before do
53
+ allow(imap).to receive(:examine).and_raise(no_method_error)
54
+ end
55
+
56
+ it "returns an empty array" do
57
+ expect(subject.uids).to eq([])
58
+ end
59
+ end
46
60
  end
47
61
 
48
- context "#fetch" do
49
- let(:message_body) { double("the body", force_encoding: nil) }
62
+ describe "#fetch" do
63
+ let(:message_body) { instance_double(String, force_encoding: nil) }
50
64
  let(:attributes) { {"RFC822" => message_body, "other" => "xxx"} }
51
65
  let(:fetch_data_item) do
52
66
  instance_double(Net::IMAP::FetchData, attr: attributes)
@@ -58,7 +72,7 @@ describe Imap::Backup::Account::Folder do
58
72
  expect(subject.fetch(123)).to eq(attributes)
59
73
  end
60
74
 
61
- context "if the server responds with nothing" do
75
+ context "when the server responds with nothing" do
62
76
  before { allow(imap).to receive(:uid_fetch) { nil } }
63
77
 
64
78
  it "is nil" do
@@ -66,7 +80,7 @@ describe Imap::Backup::Account::Folder do
66
80
  end
67
81
  end
68
82
 
69
- context "if the mailbox doesn't exist" do
83
+ context "when the mailbox doesn't exist" do
70
84
  before do
71
85
  allow(imap).to receive(:examine).and_raise(missing_mailbox_error)
72
86
  end
@@ -76,20 +90,22 @@ describe Imap::Backup::Account::Folder do
76
90
  end
77
91
  end
78
92
 
79
- it "sets the encoding on the message" do
80
- subject.fetch(123)
93
+ context "when the response doesn't have RFC822" do
94
+ let(:attributes) { {} }
81
95
 
82
- expect(message_body).to have_received(:force_encoding).with("utf-8")
96
+ it "is nil" do
97
+ expect(subject.fetch(123)).to be_nil
98
+ end
83
99
  end
84
100
  end
85
101
 
86
- context "#folder" do
102
+ describe "#folder" do
87
103
  it "is the name" do
88
104
  expect(subject.folder).to eq("my_folder")
89
105
  end
90
106
  end
91
107
 
92
- context "#exist?" do
108
+ describe "#exist?" do
93
109
  context "when the folder exists" do
94
110
  it "is true" do
95
111
  expect(subject.exist?).to be_truthy
@@ -107,62 +123,84 @@ describe Imap::Backup::Account::Folder do
107
123
  end
108
124
  end
109
125
 
110
- context "#create" do
126
+ describe "#create" do
111
127
  context "when the folder exists" do
112
- before { subject.create }
113
-
114
128
  it "is does not create the folder" do
115
- expect(imap).to_not have_received(:create)
129
+ expect(imap).to_not receive(:create)
130
+
131
+ subject.create
116
132
  end
117
133
  end
118
134
 
119
135
  context "when the folder doesn't exist" do
120
136
  before do
121
137
  allow(imap).to receive(:examine).and_raise(missing_mailbox_error)
122
- subject.create
123
138
  end
124
139
 
125
140
  it "is does not create the folder" do
126
- expect(imap).to have_received(:create)
141
+ expect(imap).to receive(:create)
142
+
143
+ subject.create
127
144
  end
128
145
  end
129
146
  end
130
147
 
131
- context "#uid_validity" do
148
+ describe "#uid_validity" do
132
149
  let(:responses) { {"UIDVALIDITY" => ["x", "uid validity"]} }
133
150
 
134
151
  it "is returned" do
135
152
  expect(subject.uid_validity).to eq("uid validity")
136
153
  end
154
+
155
+ context "when the folder doesn't exist" do
156
+ before do
157
+ allow(imap).to receive(:examine).and_raise(missing_mailbox_error)
158
+ end
159
+
160
+ it "raises an error" do
161
+ expect do
162
+ subject.uid_validity
163
+ end.to raise_error(Imap::Backup::FolderNotFound)
164
+ end
165
+ end
137
166
  end
138
167
 
139
- context "#append" do
168
+ describe "#append" do
140
169
  let(:message) do
141
170
  instance_double(
142
171
  Email::Mboxrd::Message,
143
172
  imap_body: "imap body",
144
- date: Time.now
173
+ date: message_date
145
174
  )
146
175
  end
147
- let(:append_response) { "response" }
148
- let(:result) { subject.append(message) }
149
-
150
- before do
151
- allow(append_response).
152
- to receive_message_chain("data.code.data") { "1 2" }
153
- result
176
+ let(:message_date) { Time.new(2010, 10, 10, 9, 15, 22, 0) }
177
+ let(:append_response) do
178
+ OpenStruct.new(data: OpenStruct.new(code: OpenStruct.new(data: "1 2")))
154
179
  end
155
180
 
156
181
  it "appends the message" do
157
- expect(imap).to have_received(:append)
182
+ expect(imap).to receive(:append)
183
+
184
+ subject.append(message)
185
+ end
186
+
187
+ it "sets the date and time" do
188
+ expect(imap).to receive(:append).
189
+ with(anything, anything, anything, message_date)
190
+
191
+ subject.append(message)
158
192
  end
159
193
 
160
194
  it "returns the new uid" do
161
- expect(result).to eq(2)
195
+ expect(subject.append(message)).to eq(2)
162
196
  end
163
197
 
164
198
  it "set the new uid validity" do
199
+ subject.append(message)
200
+
165
201
  expect(subject.uid_validity).to eq(1)
166
202
  end
167
203
  end
168
204
  end
205
+
206
+ # rubocop:enable RSpec/PredicateMatcher
@@ -1,30 +1,13 @@
1
- require "spec_helper"
1
+ # rubocop:disable RSpec/NestedGroups
2
2
 
3
3
  describe Imap::Backup::Configuration::Account do
4
- class MockHighlineMenu
5
- attr_reader :choices
6
- attr_accessor :header
7
-
8
- def initialize
9
- @choices = {}
10
- end
11
-
12
- def choice(name, &block)
13
- choices[name] = block
14
- end
15
-
16
- def hidden(name, &block)
17
- choices[name] = block
18
- end
19
- end
4
+ describe "#initialize" do
5
+ subject { described_class.new(store, account, highline) }
20
6
 
21
- context "#initialize" do
22
7
  let(:store) { "store" }
23
8
  let(:account) { "account" }
24
9
  let(:highline) { "highline" }
25
10
 
26
- subject { described_class.new(store, account, highline) }
27
-
28
11
  [:store, :account, :highline].each do |param|
29
12
  it "expects #{param}" do
30
13
  expect(subject.send(param)).to eq(send(param))
@@ -32,11 +15,32 @@ describe Imap::Backup::Configuration::Account do
32
15
  end
33
16
  end
34
17
 
35
- context "#run" do
36
- let(:highline) { double("Highline") }
37
- let(:menu) { MockHighlineMenu.new }
18
+ describe "#run" do
19
+ subject { described_class.new(store, account, highline) }
20
+
21
+ let(:highline_menu_class) do
22
+ Class.new do
23
+ attr_reader :choices
24
+ attr_accessor :header
25
+
26
+ def initialize
27
+ @choices = {}
28
+ end
29
+
30
+ def choice(name, &block)
31
+ choices[name] = block
32
+ end
33
+
34
+ def hidden(name, &block)
35
+ choices[name] = block
36
+ end
37
+ end
38
+ end
39
+
40
+ let(:highline) { instance_double(HighLine) }
41
+ let(:menu) { highline_menu_class.new }
38
42
  let(:store) do
39
- double("Imap::Backup::Configuration::Store", accounts: accounts)
43
+ instance_double(Imap::Backup::Configuration::Store, accounts: accounts)
40
44
  end
41
45
  let(:accounts) { [account, account1] }
42
46
  let(:account) do
@@ -45,13 +49,13 @@ describe Imap::Backup::Configuration::Account do
45
49
  server: existing_server,
46
50
  local_path: "/backup/path",
47
51
  folders: [{name: "my_folder"}],
48
- password: existing_password,
52
+ password: existing_password
49
53
  }
50
54
  end
51
55
  let(:account1) do
52
56
  {
53
57
  username: other_email,
54
- local_path: other_existing_path,
58
+ local_path: other_existing_path
55
59
  }
56
60
  end
57
61
  let(:existing_email) { "user@example.com" }
@@ -62,31 +66,31 @@ describe Imap::Backup::Configuration::Account do
62
66
  let(:other_existing_path) { "/other/existing/path" }
63
67
 
64
68
  before do
65
- allow(subject).to receive(:system).and_return(nil)
66
- allow(subject).to receive(:puts).and_return(nil)
69
+ allow(Kernel).to receive(:system)
70
+ allow(Kernel).to receive(:puts)
67
71
  allow(highline).to receive(:choose) do |&block|
68
72
  block.call(menu)
69
73
  throw :done
70
74
  end
71
75
  end
72
76
 
73
- subject { described_class.new(store, account, highline) }
74
-
75
- context "preparation" do
76
- before { subject.run }
77
-
77
+ describe "preparation" do
78
78
  it "clears the screen" do
79
- expect(subject).to have_received(:system).with("clear")
79
+ expect(Kernel).to receive(:system).with("clear")
80
+
81
+ subject.run
80
82
  end
81
83
 
82
- context "menu" do
84
+ describe "menu" do
83
85
  it "shows the menu" do
84
- expect(highline).to have_received(:choose)
86
+ expect(highline).to receive(:choose)
87
+
88
+ subject.run
85
89
  end
86
90
  end
87
91
  end
88
92
 
89
- context "menu" do
93
+ describe "menu" do
90
94
  [
91
95
  "modify email",
92
96
  "modify password",
@@ -96,7 +100,7 @@ describe Imap::Backup::Configuration::Account do
96
100
  "test connection",
97
101
  "delete",
98
102
  "return to main menu",
99
- "quit", # TODO: quit is hidden
103
+ "quit" # TODO: quit is hidden
100
104
  ].each do |item|
101
105
  before { subject.run }
102
106
 
@@ -106,7 +110,7 @@ describe Imap::Backup::Configuration::Account do
106
110
  end
107
111
  end
108
112
 
109
- context "account details" do
113
+ describe "account details" do
110
114
  [
111
115
  ["email", /email:\s+user@example.com/],
112
116
  ["server", /server:\s+imap.example.com/],
@@ -132,7 +136,7 @@ describe Imap::Backup::Configuration::Account do
132
136
  end
133
137
  end
134
138
 
135
- context "email" do
139
+ describe "email" do
136
140
  before do
137
141
  allow(Imap::Backup::Configuration::Asker).
138
142
  to receive(:email) { new_email }
@@ -140,7 +144,7 @@ describe Imap::Backup::Configuration::Account do
140
144
  menu.choices["modify email"].call
141
145
  end
142
146
 
143
- context "if the server is blank" do
147
+ context "when the server is blank" do
144
148
  [
145
149
  ["GMail", "foo@gmail.com", "imap.gmail.com"],
146
150
  ["Fastmail", "bar@fastmail.fm", "imap.fastmail.com"],
@@ -166,9 +170,24 @@ describe Imap::Backup::Configuration::Account do
166
170
  end
167
171
  end
168
172
  end
173
+
174
+ context "when the domain is unrecognized" do
175
+ let(:existing_server) { nil }
176
+ let(:provider) do
177
+ instance_double(Email::Provider, provider: :default)
178
+ end
179
+
180
+ before do
181
+ allow(Email::Provider).to receive(:for_address) { provider }
182
+ end
183
+
184
+ it "does not set a default server" do
185
+ expect(account[:server]).to be_nil
186
+ end
187
+ end
169
188
  end
170
189
 
171
- context "the email is new" do
190
+ context "when the email is new" do
172
191
  it "modifies the email address" do
173
192
  expect(account[:username]).to eq(new_email)
174
193
  end
@@ -176,11 +195,11 @@ describe Imap::Backup::Configuration::Account do
176
195
  include_examples "it flags the account as modified"
177
196
  end
178
197
 
179
- context "the email already exists" do
198
+ context "when the email already exists" do
180
199
  let(:new_email) { other_email }
181
200
 
182
201
  it "indicates the error" do
183
- expect(subject).to have_received(:puts).
202
+ expect(Kernel).to have_received(:puts).
184
203
  with("There is already an account set up with that email address")
185
204
  end
186
205
 
@@ -192,7 +211,7 @@ describe Imap::Backup::Configuration::Account do
192
211
  end
193
212
  end
194
213
 
195
- context "password" do
214
+ describe "password" do
196
215
  let(:new_password) { "new_password" }
197
216
 
198
217
  before do
@@ -202,7 +221,7 @@ describe Imap::Backup::Configuration::Account do
202
221
  menu.choices["modify password"].call
203
222
  end
204
223
 
205
- context "if the user enters a password" do
224
+ context "when the user enters a password" do
206
225
  it "updates the password" do
207
226
  expect(account[:password]).to eq(new_password)
208
227
  end
@@ -210,7 +229,7 @@ describe Imap::Backup::Configuration::Account do
210
229
  include_examples "it flags the account as modified"
211
230
  end
212
231
 
213
- context "if the user cancels" do
232
+ context "when the user cancels" do
214
233
  let(:new_password) { nil }
215
234
 
216
235
  it "does nothing" do
@@ -221,15 +240,14 @@ describe Imap::Backup::Configuration::Account do
221
240
  end
222
241
  end
223
242
 
224
- context "server" do
243
+ describe "server" do
225
244
  let(:server) { "server" }
226
245
 
227
246
  before do
228
- allow(highline).to receive(:ask).with("server: ").and_return(server)
229
- end
247
+ allow(highline).to receive(:ask).with("server: ") { server }
230
248
 
231
- before do
232
249
  subject.run
250
+
233
251
  menu.choices["modify server"].call
234
252
  end
235
253
 
@@ -240,14 +258,14 @@ describe Imap::Backup::Configuration::Account do
240
258
  include_examples "it flags the account as modified"
241
259
  end
242
260
 
243
- context "backup_path" do
261
+ describe "backup_path" do
244
262
  let(:new_backup_path) { "/new/path" }
245
263
 
246
264
  before do
247
265
  @validator = nil
248
266
  allow(
249
267
  Imap::Backup::Configuration::Asker
250
- ).to receive(:backup_path) do |path, validator|
268
+ ).to receive(:backup_path) do |_path, validator|
251
269
  @validator = validator
252
270
  new_backup_path
253
271
  end
@@ -259,15 +277,29 @@ describe Imap::Backup::Configuration::Account do
259
277
  expect(account[:local_path]).to eq(new_backup_path)
260
278
  end
261
279
 
262
- it "validates that the path is not used by other backups" do
263
- expect(@validator.call(other_existing_path)).to be_falsey
280
+ context "when the path is not used by other backups" do
281
+ it "is accepts it" do
282
+ # rubocop:disable RSpec/InstanceVariable
283
+ expect(@validator.call("/unknown/path")).to be_truthy
284
+ # rubocop:enable RSpec/InstanceVariable
285
+ end
286
+ end
287
+
288
+ context "when the path is used by other backups" do
289
+ it "fails validation" do
290
+ # rubocop:disable RSpec/InstanceVariable
291
+ expect(@validator.call(other_existing_path)).to be_falsey
292
+ # rubocop:enable RSpec/InstanceVariable
293
+ end
264
294
  end
265
295
 
266
296
  include_examples "it flags the account as modified"
267
297
  end
268
298
 
269
- context "folders" do
270
- let(:chooser) { double(run: nil) }
299
+ describe "folders" do
300
+ let(:chooser) do
301
+ instance_double(Imap::Backup::Configuration::FolderChooser, run: nil)
302
+ end
271
303
 
272
304
  before do
273
305
  allow(Imap::Backup::Configuration::FolderChooser).
@@ -281,10 +313,10 @@ describe Imap::Backup::Configuration::Account do
281
313
  end
282
314
  end
283
315
 
284
- context "connection test" do
316
+ describe "connection test" do
285
317
  before do
286
318
  allow(Imap::Backup::Configuration::ConnectionTester).
287
- to receive(:test).and_return("All fine")
319
+ to receive(:test) { "All fine" }
288
320
  allow(highline).to receive(:ask)
289
321
  subject.run
290
322
  menu.choices["test connection"].call
@@ -296,11 +328,11 @@ describe Imap::Backup::Configuration::Account do
296
328
  end
297
329
  end
298
330
 
299
- context "deletion" do
331
+ describe "deletion" do
300
332
  let(:confirmed) { true }
301
333
 
302
334
  before do
303
- allow(highline).to receive(:agree).and_return(confirmed)
335
+ allow(highline).to receive(:agree) { confirmed }
304
336
  subject.run
305
337
  catch :done do
306
338
  menu.choices["delete"].call
@@ -323,3 +355,5 @@ describe Imap::Backup::Configuration::Account do
323
355
  end
324
356
  end
325
357
  end
358
+
359
+ # rubocop:enable RSpec/NestedGroups