imap-backup 2.1.0 → 2.1.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (37) hide show
  1. checksums.yaml +4 -4
  2. data/.rspec +0 -1
  3. data/.rubocop.yml +5 -2
  4. data/.rubocop_todo.yml +24 -0
  5. data/.travis.yml +13 -1
  6. data/README.md +9 -21
  7. data/Rakefile +6 -2
  8. data/imap-backup.gemspec +5 -1
  9. data/lib/email/mboxrd/message.rb +13 -12
  10. data/lib/imap/backup/account/connection.rb +3 -2
  11. data/lib/imap/backup/account/folder.rb +2 -0
  12. data/lib/imap/backup/configuration/account.rb +20 -16
  13. data/lib/imap/backup/configuration/asker.rb +1 -1
  14. data/lib/imap/backup/serializer/mbox.rb +39 -25
  15. data/lib/imap/backup/serializer/mbox_enumerator.rb +31 -0
  16. data/lib/imap/backup/serializer/mbox_store.rb +6 -27
  17. data/lib/imap/backup/version.rb +1 -1
  18. data/spec/features/support/email_server.rb +3 -0
  19. data/spec/support/fixtures.rb +1 -1
  20. data/spec/unit/email/mboxrd/message_spec.rb +20 -3
  21. data/spec/unit/email/provider_spec.rb +1 -1
  22. data/spec/unit/imap/backup/account/connection_spec.rb +10 -9
  23. data/spec/unit/imap/backup/account/folder_spec.rb +24 -10
  24. data/spec/unit/imap/backup/configuration/account_spec.rb +47 -22
  25. data/spec/unit/imap/backup/configuration/asker_spec.rb +5 -9
  26. data/spec/unit/imap/backup/configuration/connection_tester_spec.rb +6 -6
  27. data/spec/unit/imap/backup/configuration/folder_chooser_spec.rb +6 -6
  28. data/spec/unit/imap/backup/configuration/list_spec.rb +24 -1
  29. data/spec/unit/imap/backup/configuration/setup_spec.rb +29 -9
  30. data/spec/unit/imap/backup/configuration/store_spec.rb +5 -5
  31. data/spec/unit/imap/backup/downloader_spec.rb +11 -13
  32. data/spec/unit/imap/backup/serializer/mbox_enumerator_spec.rb +40 -0
  33. data/spec/unit/imap/backup/serializer/mbox_spec.rb +63 -24
  34. data/spec/unit/imap/backup/serializer/mbox_store_spec.rb +162 -9
  35. data/spec/unit/imap/backup/utils_spec.rb +3 -3
  36. data/spec/unit/imap/backup_spec.rb +28 -0
  37. metadata +8 -2
@@ -1,6 +1,7 @@
1
1
  require "json"
2
2
 
3
3
  require "email/mboxrd/message"
4
+ require "imap/backup/serializer/mbox_enumerator"
4
5
 
5
6
  module Imap::Backup
6
7
  class Serializer::MboxStore
@@ -71,8 +72,9 @@ module Imap::Backup
71
72
  end
72
73
  end
73
74
 
74
- def load(uid)
75
+ def load(uid_maybe_string)
75
76
  do_load if !loaded
77
+ uid = uid_maybe_string.to_i
76
78
  message_index = uids.find_index(uid)
77
79
  return nil if message_index.nil?
78
80
 
@@ -103,16 +105,12 @@ module Imap::Backup
103
105
  @folder = new_name
104
106
  end
105
107
 
106
- def relative_path
107
- File.dirname(folder)
108
- end
109
-
110
108
  private
111
109
 
112
110
  def do_load
113
111
  data = imap_data
114
112
  if data
115
- @uids = data[:uids].map(&:to_i).sort
113
+ @uids = data[:uids].map(&:to_i)
116
114
  @uid_validity = data[:uid_validity]
117
115
  @loaded = true
118
116
  else
@@ -145,7 +143,8 @@ module Imap::Backup
145
143
  end
146
144
 
147
145
  def load_nth(index)
148
- each_mbox_message.with_index do |raw, i|
146
+ enumerator = Serializer::MboxEnumerator.new(mbox_pathname)
147
+ enumerator.each.with_index do |raw, i|
149
148
  next unless i == index
150
149
 
151
150
  return Email::Mboxrd::Message.from_serialized(raw)
@@ -153,26 +152,6 @@ module Imap::Backup
153
152
  nil
154
153
  end
155
154
 
156
- def each_mbox_message
157
- Enumerator.new do |e|
158
- File.open(mbox_pathname) do |f|
159
- lines = []
160
-
161
- loop do
162
- line = f.gets
163
- break if !line
164
- if line.start_with?("From ")
165
- e.yield lines.join if lines.count.positive?
166
- lines = [line]
167
- else
168
- lines << line
169
- end
170
- end
171
- e.yield lines.join if lines.count.positive?
172
- end
173
- end
174
- end
175
-
176
155
  def imap_looks_like_json?
177
156
  return false unless imap_exist?
178
157
 
@@ -3,6 +3,6 @@ module Imap; end
3
3
  module Imap::Backup
4
4
  MAJOR = 2
5
5
  MINOR = 1
6
- REVISION = 0
6
+ REVISION = 1
7
7
  VERSION = [MAJOR, MINOR, REVISION].compact.map(&:to_s).join(".")
8
8
  end
@@ -85,6 +85,9 @@ module EmailServerHelpers
85
85
  def server_delete_folder(folder)
86
86
  imap.delete folder
87
87
  imap.disconnect
88
+ rescue StandardError => e
89
+ puts e.to_s
90
+ ensure
88
91
  @imap = nil
89
92
  end
90
93
 
@@ -2,5 +2,5 @@ def fixture(name)
2
2
  spec_root = File.expand_path("..", File.dirname(__FILE__))
3
3
  fixture_path = File.join(spec_root, "fixtures", name + ".yml")
4
4
  fixture = File.read(fixture_path)
5
- YAML.safe_load(fixture, permitted_classes: [Symbol])
5
+ YAML.safe_load(fixture, [Symbol])
6
6
  end
@@ -72,7 +72,7 @@ describe Email::Mboxrd::Message do
72
72
  end
73
73
  end
74
74
 
75
- context "#to_serialized" do
75
+ describe "#to_serialized" do
76
76
  let(:mail) { instance_double(Mail::Message, from: [from], date: date) }
77
77
 
78
78
  before do
@@ -107,7 +107,7 @@ describe Email::Mboxrd::Message do
107
107
  end
108
108
  end
109
109
 
110
- context "#from" do
110
+ describe "From" do
111
111
  before do
112
112
  # call original for these tests because we want to test the behaviour of
113
113
  # class-under-test given different behaviour of the Mail parser
@@ -148,7 +148,7 @@ describe Email::Mboxrd::Message do
148
148
  end
149
149
  end
150
150
 
151
- context "#date" do
151
+ describe "#date" do
152
152
  let(:message_body) { msg_good }
153
153
 
154
154
  it "returns the date" do
@@ -163,4 +163,21 @@ describe Email::Mboxrd::Message do
163
163
  end
164
164
  end
165
165
  end
166
+
167
+ describe "#imap_body" do
168
+ let(:message_body) { "Ciao" }
169
+
170
+ it "returns the supplied body" do
171
+ expect(subject.imap_body).to eq(message_body)
172
+ end
173
+
174
+ context "when newlines are not IMAP standard" do
175
+ let(:message_body) { "Ciao\nHello" }
176
+ let(:corrected) { "Ciao\r\nHello" }
177
+
178
+ it "corrects them" do
179
+ expect(subject.imap_body).to eq(corrected)
180
+ end
181
+ end
182
+ end
166
183
  end
@@ -1,6 +1,6 @@
1
1
  describe Email::Provider do
2
2
  describe ".for_address" do
3
- context "known providers" do
3
+ context "with known providers" do
4
4
  [
5
5
  ["gmail.com", :gmail],
6
6
  ["fastmail.fm", :fastmail]
@@ -33,7 +33,7 @@ describe Imap::Backup::Account::Connection do
33
33
  Imap::Backup::Serializer::Mbox,
34
34
  folder: serialized_folder,
35
35
  force_uid_validity: nil,
36
- set_uid_validity: new_uid_validity,
36
+ apply_uid_validity: new_uid_validity,
37
37
  uids: [local_uid]
38
38
  )
39
39
  end
@@ -58,7 +58,7 @@ describe Imap::Backup::Account::Connection do
58
58
  end
59
59
  end
60
60
 
61
- context "#initialize" do
61
+ describe "#initialize" do
62
62
  [
63
63
  [:username, "username@gmail.com"],
64
64
  [:local_path, "local_path"],
@@ -85,7 +85,7 @@ describe Imap::Backup::Account::Connection do
85
85
  include_examples "connects to IMAP"
86
86
  end
87
87
 
88
- context "#folders" do
88
+ describe "#folders" do
89
89
  let(:imap_folders) do
90
90
  [instance_double(Net::IMAP::MailboxList)]
91
91
  end
@@ -95,7 +95,7 @@ describe Imap::Backup::Account::Connection do
95
95
  end
96
96
  end
97
97
 
98
- context "#status" do
98
+ describe "#status" do
99
99
  let(:folder) do
100
100
  instance_double(Imap::Backup::Account::Folder, uids: [remote_uid])
101
101
  end
@@ -119,7 +119,7 @@ describe Imap::Backup::Account::Connection do
119
119
  end
120
120
  end
121
121
 
122
- context "#run_backup" do
122
+ describe "#run_backup" do
123
123
  let(:folder) do
124
124
  instance_double(
125
125
  Imap::Backup::Account::Folder,
@@ -202,7 +202,7 @@ describe Imap::Backup::Account::Connection do
202
202
  end
203
203
  end
204
204
 
205
- context "#restore" do
205
+ describe "#restore" do
206
206
  let(:folder) do
207
207
  instance_double(
208
208
  Imap::Backup::Account::Folder,
@@ -253,7 +253,8 @@ describe Imap::Backup::Account::Connection do
253
253
  end
254
254
 
255
255
  it "sets local uid validity" do
256
- expect(serializer).to have_received(:set_uid_validity).with(uid_validity)
256
+ expect(serializer).
257
+ to have_received(:apply_uid_validity).with(uid_validity)
257
258
  end
258
259
 
259
260
  context "when folders exist" do
@@ -297,7 +298,7 @@ describe Imap::Backup::Account::Connection do
297
298
  end
298
299
  end
299
300
 
300
- context "#reconnect" do
301
+ describe "#reconnect" do
301
302
  before { subject.reconnect }
302
303
 
303
304
  it "disconnects from the server" do
@@ -311,7 +312,7 @@ describe Imap::Backup::Account::Connection do
311
312
  end
312
313
  end
313
314
 
314
- context "#disconnect" do
315
+ describe "#disconnect" do
315
316
  before { subject.disconnect }
316
317
 
317
318
  it "disconnects from the server" do
@@ -25,7 +25,7 @@ describe Imap::Backup::Account::Folder do
25
25
  let(:responses) { [] }
26
26
  let(:append_response) { nil }
27
27
 
28
- context "#uids" do
28
+ describe "#uids" do
29
29
  let(:uids) { [5678, 123] }
30
30
 
31
31
  before { allow(imap).to receive(:uid_search).and_return(uids) }
@@ -45,7 +45,7 @@ describe Imap::Backup::Account::Folder do
45
45
  end
46
46
  end
47
47
 
48
- context "#fetch" do
48
+ describe "#fetch" do
49
49
  let(:message_body) { instance_double(String, force_encoding: nil) }
50
50
  let(:attributes) { {"RFC822" => message_body, "other" => "xxx"} }
51
51
  let(:fetch_data_item) do
@@ -58,7 +58,7 @@ describe Imap::Backup::Account::Folder do
58
58
  expect(subject.fetch(123)).to eq(attributes)
59
59
  end
60
60
 
61
- context "if the server responds with nothing" do
61
+ context "when the server responds with nothing" do
62
62
  before { allow(imap).to receive(:uid_fetch) { nil } }
63
63
 
64
64
  it "is nil" do
@@ -66,7 +66,7 @@ describe Imap::Backup::Account::Folder do
66
66
  end
67
67
  end
68
68
 
69
- context "if the mailbox doesn't exist" do
69
+ context "when the mailbox doesn't exist" do
70
70
  before do
71
71
  allow(imap).to receive(:examine).and_raise(missing_mailbox_error)
72
72
  end
@@ -76,6 +76,14 @@ describe Imap::Backup::Account::Folder do
76
76
  end
77
77
  end
78
78
 
79
+ context "when the response doesn't have RFC822" do
80
+ let(:attributes) { {} }
81
+
82
+ it "is nil" do
83
+ expect(subject.fetch(123)).to be_nil
84
+ end
85
+ end
86
+
79
87
  it "sets the encoding on the message" do
80
88
  subject.fetch(123)
81
89
 
@@ -83,13 +91,13 @@ describe Imap::Backup::Account::Folder do
83
91
  end
84
92
  end
85
93
 
86
- context "#folder" do
94
+ describe "#folder" do
87
95
  it "is the name" do
88
96
  expect(subject.folder).to eq("my_folder")
89
97
  end
90
98
  end
91
99
 
92
- context "#exist?" do
100
+ describe "#exist?" do
93
101
  context "when the folder exists" do
94
102
  it "is true" do
95
103
  expect(subject.exist?).to be_truthy
@@ -107,7 +115,7 @@ describe Imap::Backup::Account::Folder do
107
115
  end
108
116
  end
109
117
 
110
- context "#create" do
118
+ describe "#create" do
111
119
  context "when the folder exists" do
112
120
  before { subject.create }
113
121
 
@@ -128,7 +136,7 @@ describe Imap::Backup::Account::Folder do
128
136
  end
129
137
  end
130
138
 
131
- context "#uid_validity" do
139
+ describe "#uid_validity" do
132
140
  let(:responses) { {"UIDVALIDITY" => ["x", "uid validity"]} }
133
141
 
134
142
  it "is returned" do
@@ -148,14 +156,15 @@ describe Imap::Backup::Account::Folder do
148
156
  end
149
157
  end
150
158
 
151
- context "#append" do
159
+ describe "#append" do
152
160
  let(:message) do
153
161
  instance_double(
154
162
  Email::Mboxrd::Message,
155
163
  imap_body: "imap body",
156
- date: Time.now
164
+ date: message_date
157
165
  )
158
166
  end
167
+ let(:message_date) { Time.new(2010, 10, 10, 9, 15, 22, 0) }
159
168
  let(:append_response) do
160
169
  OpenStruct.new(data: OpenStruct.new(code: OpenStruct.new(data: "1 2")))
161
170
  end
@@ -169,6 +178,11 @@ describe Imap::Backup::Account::Folder do
169
178
  expect(imap).to have_received(:append)
170
179
  end
171
180
 
181
+ it "sets the date and time" do
182
+ expect(imap).to have_received(:append).
183
+ with(anything, anything, anything, message_date)
184
+ end
185
+
172
186
  it "returns the new uid" do
173
187
  expect(result).to eq(2)
174
188
  end
@@ -18,7 +18,7 @@ describe Imap::Backup::Configuration::Account do
18
18
  end
19
19
  end
20
20
 
21
- context "#initialize" do
21
+ describe "#initialize" do
22
22
  subject { described_class.new(store, account, highline) }
23
23
 
24
24
  let(:store) { "store" }
@@ -32,7 +32,7 @@ describe Imap::Backup::Configuration::Account do
32
32
  end
33
33
  end
34
34
 
35
- context "#run" do
35
+ describe "#run" do
36
36
  subject { described_class.new(store, account, highline) }
37
37
 
38
38
  let(:highline) { instance_double(HighLine) }
@@ -72,21 +72,21 @@ describe Imap::Backup::Configuration::Account do
72
72
  end
73
73
  end
74
74
 
75
- context "preparation" do
75
+ describe "preparation" do
76
76
  before { subject.run }
77
77
 
78
78
  it "clears the screen" do
79
79
  expect(Kernel).to have_received(:system).with("clear")
80
80
  end
81
81
 
82
- context "menu" do
82
+ describe "menu" do
83
83
  it "shows the menu" do
84
84
  expect(highline).to have_received(:choose)
85
85
  end
86
86
  end
87
87
  end
88
88
 
89
- context "menu" do
89
+ describe "menu" do
90
90
  [
91
91
  "modify email",
92
92
  "modify password",
@@ -106,7 +106,7 @@ describe Imap::Backup::Configuration::Account do
106
106
  end
107
107
  end
108
108
 
109
- context "account details" do
109
+ describe "account details" do
110
110
  [
111
111
  ["email", /email:\s+user@example.com/],
112
112
  ["server", /server:\s+imap.example.com/],
@@ -132,7 +132,7 @@ describe Imap::Backup::Configuration::Account do
132
132
  end
133
133
  end
134
134
 
135
- context "email" do
135
+ describe "email" do
136
136
  before do
137
137
  allow(Imap::Backup::Configuration::Asker).
138
138
  to receive(:email) { new_email }
@@ -140,7 +140,7 @@ describe Imap::Backup::Configuration::Account do
140
140
  menu.choices["modify email"].call
141
141
  end
142
142
 
143
- context "if the server is blank" do
143
+ context "when the server is blank" do
144
144
  [
145
145
  ["GMail", "foo@gmail.com", "imap.gmail.com"],
146
146
  ["Fastmail", "bar@fastmail.fm", "imap.fastmail.com"],
@@ -166,9 +166,24 @@ describe Imap::Backup::Configuration::Account do
166
166
  end
167
167
  end
168
168
  end
169
+
170
+ context "when the domain is unrecognized" do
171
+ let(:existing_server) { nil }
172
+ let(:provider) do
173
+ instance_double(Email::Provider, provider: :default)
174
+ end
175
+
176
+ before do
177
+ allow(Email::Provider).to receive(:for_address) { provider }
178
+ end
179
+
180
+ it "does not set a default server" do
181
+ expect(account[:server]).to be_nil
182
+ end
183
+ end
169
184
  end
170
185
 
171
- context "the email is new" do
186
+ context "when the email is new" do
172
187
  it "modifies the email address" do
173
188
  expect(account[:username]).to eq(new_email)
174
189
  end
@@ -176,7 +191,7 @@ describe Imap::Backup::Configuration::Account do
176
191
  include_examples "it flags the account as modified"
177
192
  end
178
193
 
179
- context "the email already exists" do
194
+ context "when the email already exists" do
180
195
  let(:new_email) { other_email }
181
196
 
182
197
  it "indicates the error" do
@@ -192,7 +207,7 @@ describe Imap::Backup::Configuration::Account do
192
207
  end
193
208
  end
194
209
 
195
- context "password" do
210
+ describe "password" do
196
211
  let(:new_password) { "new_password" }
197
212
 
198
213
  before do
@@ -202,7 +217,7 @@ describe Imap::Backup::Configuration::Account do
202
217
  menu.choices["modify password"].call
203
218
  end
204
219
 
205
- context "if the user enters a password" do
220
+ context "when the user enters a password" do
206
221
  it "updates the password" do
207
222
  expect(account[:password]).to eq(new_password)
208
223
  end
@@ -210,7 +225,7 @@ describe Imap::Backup::Configuration::Account do
210
225
  include_examples "it flags the account as modified"
211
226
  end
212
227
 
213
- context "if the user cancels" do
228
+ context "when the user cancels" do
214
229
  let(:new_password) { nil }
215
230
 
216
231
  it "does nothing" do
@@ -221,7 +236,7 @@ describe Imap::Backup::Configuration::Account do
221
236
  end
222
237
  end
223
238
 
224
- context "server" do
239
+ describe "server" do
225
240
  let(:server) { "server" }
226
241
 
227
242
  before do
@@ -237,7 +252,7 @@ describe Imap::Backup::Configuration::Account do
237
252
  include_examples "it flags the account as modified"
238
253
  end
239
254
 
240
- context "backup_path" do
255
+ describe "backup_path" do
241
256
  let(:new_backup_path) { "/new/path" }
242
257
 
243
258
  before do
@@ -256,16 +271,26 @@ describe Imap::Backup::Configuration::Account do
256
271
  expect(account[:local_path]).to eq(new_backup_path)
257
272
  end
258
273
 
259
- it "validates that the path is not used by other backups" do
260
- # rubocop:disable RSpec/InstanceVariable
261
- expect(@validator.call(other_existing_path)).to be_falsey
262
- # rubocop:enable RSpec/InstanceVariable
274
+ context "when the path is not used by other backups" do
275
+ it "is accepts it" do
276
+ # rubocop:disable RSpec/InstanceVariable
277
+ expect(@validator.call("/unknown/path")).to be_truthy
278
+ # rubocop:enable RSpec/InstanceVariable
279
+ end
280
+ end
281
+
282
+ context "when the path is used by other backups" do
283
+ it "fails validation" do
284
+ # rubocop:disable RSpec/InstanceVariable
285
+ expect(@validator.call(other_existing_path)).to be_falsey
286
+ # rubocop:enable RSpec/InstanceVariable
287
+ end
263
288
  end
264
289
 
265
290
  include_examples "it flags the account as modified"
266
291
  end
267
292
 
268
- context "folders" do
293
+ describe "folders" do
269
294
  let(:chooser) do
270
295
  instance_double(Imap::Backup::Configuration::FolderChooser, run: nil)
271
296
  end
@@ -282,7 +307,7 @@ describe Imap::Backup::Configuration::Account do
282
307
  end
283
308
  end
284
309
 
285
- context "connection test" do
310
+ describe "connection test" do
286
311
  before do
287
312
  allow(Imap::Backup::Configuration::ConnectionTester).
288
313
  to receive(:test).and_return("All fine")
@@ -297,7 +322,7 @@ describe Imap::Backup::Configuration::Account do
297
322
  end
298
323
  end
299
324
 
300
- context "deletion" do
325
+ describe "deletion" do
301
326
  let(:confirmed) { true }
302
327
 
303
328
  before do