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,9 +1,9 @@
1
- require "spec_helper"
1
+ # rubocop:disable RSpec/NestedGroups
2
2
 
3
3
  describe Imap::Backup::Configuration::Setup do
4
4
  include HighLineTestHelpers
5
5
 
6
- context "#initialize" do
6
+ describe "#initialize" do
7
7
  context "without a config file" do
8
8
  it "works" do
9
9
  described_class.new
@@ -11,83 +11,105 @@ describe Imap::Backup::Configuration::Setup do
11
11
  end
12
12
  end
13
13
 
14
- context "#run" do
14
+ describe "#run" do
15
+ subject { described_class.new }
16
+
15
17
  let(:normal) { {username: "account@example.com"} }
16
18
  let(:accounts) { [normal] }
17
19
  let(:store) do
18
- double(
19
- "Imap::Backup::Configuration::Store",
20
- :accounts => accounts,
21
- :path => "/base/path",
22
- :save => nil,
23
- :debug? => debug,
24
- :debug= => nil,
25
- :modified? => modified,
20
+ instance_double(
21
+ Imap::Backup::Configuration::Store,
22
+ "accounts": accounts,
23
+ "path": "/base/path",
24
+ "save": nil,
25
+ "debug?": debug,
26
+ "debug=": nil,
27
+ "modified?": modified
26
28
  )
27
29
  end
28
30
  let(:debug) { false }
29
31
  let(:modified) { false }
32
+ let!(:highline_streams) { prepare_highline }
33
+ let(:input) { highline_streams[0] }
34
+ let(:output) { highline_streams[1] }
30
35
 
31
- before :each do
36
+ before do
32
37
  allow(Imap::Backup::Configuration::Store).to receive(:new) { store }
33
38
  allow(Imap::Backup).to receive(:setup_logging)
34
- @input, @output = prepare_highline
35
- allow(@input).to receive(:eof?).and_return(false)
36
- allow(@input).to receive(:gets).and_return("exit\n")
37
- allow(subject).to receive(:system)
39
+ allow(input).to receive(:eof?) { false }
40
+ allow(input).to receive(:gets) { "exit\n" }
41
+ allow(Kernel).to receive(:system)
38
42
  end
39
43
 
40
- subject { described_class.new }
41
-
42
- context "main menu" do
44
+ describe "main menu" do
43
45
  before { subject.run }
44
46
 
45
47
  %w(add\ account save\ and\ exit exit\ without\ saving).each do |choice|
46
48
  it "includes #{choice}" do
47
- expect(@output.string).to include(choice)
49
+ expect(output.string).to include(choice)
48
50
  end
49
51
  end
50
52
  end
51
53
 
52
54
  it "clears the screen" do
53
- subject.run
55
+ expect(Kernel).to receive(:system).with("clear")
54
56
 
55
- expect(subject).to have_received(:system).with("clear")
57
+ subject.run
56
58
  end
57
59
 
58
60
  it "updates logging status" do
59
- subject.run
61
+ expect(Imap::Backup).to receive(:setup_logging)
60
62
 
61
- expect(Imap::Backup).to have_received(:setup_logging)
63
+ subject.run
62
64
  end
63
65
 
64
- context "listing" do
66
+ describe "listing" do
65
67
  let(:accounts) { [normal, modified, deleted] }
66
68
  let(:modified) { {username: "modified@example.com", modified: true} }
67
69
  let(:deleted) { {username: "deleted@example.com", delete: true} }
68
70
 
69
71
  before { subject.run }
70
72
 
71
- context "normal accounts" do
73
+ describe "normal accounts" do
72
74
  it "are listed" do
73
- expect(@output.string).to match /account@example.com/
75
+ expect(output.string).to match(/account@example.com/)
74
76
  end
75
77
  end
76
78
 
77
- context "modified accounts" do
79
+ describe "modified accounts" do
78
80
  it "are flagged" do
79
- expect(@output.string).to match /modified@example.com \*/
81
+ expect(output.string).to match(/modified@example.com \*/)
80
82
  end
81
83
  end
82
84
 
83
- context "deleted accounts" do
85
+ describe "deleted accounts" do
84
86
  it "are hidden" do
85
- expect(@output.string).to_not match /delete@example.com/
87
+ expect(output.string).to_not match(/delete@example.com/)
86
88
  end
87
89
  end
88
90
  end
89
91
 
90
- context "adding accounts" do
92
+ context "when editing accounts" do
93
+ let(:account) do
94
+ instance_double(Imap::Backup::Configuration::Account, run: nil)
95
+ end
96
+
97
+ before do
98
+ allow(input).to receive(:gets).and_return("1\n", "exit\n")
99
+ allow(Imap::Backup::Configuration::Asker).to receive(:email).
100
+ with(no_args) { "new@example.com" }
101
+ allow(Imap::Backup::Configuration::Account).to receive(:new).
102
+ with(store, normal, anything) { account }
103
+ end
104
+
105
+ it "edits the account" do
106
+ expect(account).to receive(:run)
107
+
108
+ subject.run
109
+ end
110
+ end
111
+
112
+ context "when adding accounts" do
91
113
  let(:blank_account) do
92
114
  {
93
115
  username: "new@example.com",
@@ -96,14 +118,16 @@ describe Imap::Backup::Configuration::Setup do
96
118
  folders: []
97
119
  }
98
120
  end
99
- let(:account) { double("Imap::Backup::Configuration::Account", run: nil) }
121
+ let(:account) do
122
+ instance_double(Imap::Backup::Configuration::Account, run: nil)
123
+ end
100
124
 
101
125
  before do
102
- allow(@input).to receive(:gets).and_return("add\n", "exit\n")
126
+ allow(input).to receive(:gets).and_return("add\n", "exit\n")
103
127
  allow(Imap::Backup::Configuration::Asker).to receive(:email).
104
- with(no_args).and_return("new@example.com")
128
+ with(no_args) { "new@example.com" }
105
129
  allow(Imap::Backup::Configuration::Account).to receive(:new).
106
- with(store, blank_account, anything).and_return(account)
130
+ with(store, blank_account, anything) { account }
107
131
 
108
132
  subject.run
109
133
  end
@@ -117,24 +141,29 @@ describe Imap::Backup::Configuration::Setup do
117
141
  end
118
142
  end
119
143
 
120
- context "logging" do
144
+ describe "logging" do
121
145
  context "when debug logging is disabled" do
122
146
  before do
123
- allow(@input).to receive(:gets).and_return("start\n", "exit\n")
124
- subject.run
147
+ allow(input).to receive(:gets).and_return("start\n", "exit\n")
125
148
  end
126
149
 
127
150
  it "shows a menu item" do
128
- expect(@output.string).to include("start logging")
151
+ subject.run
152
+
153
+ expect(output.string).to include("start logging")
129
154
  end
130
155
 
131
156
  context "when selected" do
132
157
  it "sets the debug flag" do
133
- expect(store).to have_received(:debug=).with(true)
158
+ expect(store).to receive(:debug=).with(true)
159
+
160
+ subject.run
134
161
  end
135
162
 
136
163
  it "updates logging status" do
137
- expect(Imap::Backup).to have_received(:setup_logging).twice
164
+ expect(Imap::Backup).to receive(:setup_logging).twice
165
+
166
+ subject.run
138
167
  end
139
168
  end
140
169
  end
@@ -143,25 +172,30 @@ describe Imap::Backup::Configuration::Setup do
143
172
  let(:debug) { true }
144
173
 
145
174
  before do
146
- allow(@input).to receive(:gets).and_return("stop\n", "exit\n")
147
- subject.run
175
+ allow(input).to receive(:gets).and_return("stop\n", "exit\n")
148
176
  end
149
177
 
150
178
  it "shows a menu item" do
151
- expect(@output.string).to include("stop logging")
179
+ subject.run
180
+
181
+ expect(output.string).to include("stop logging")
152
182
  end
153
183
 
154
184
  context "when selected" do
155
185
  before do
156
- allow(@input).to receive(:gets).and_return("stop\n", "exit\n")
186
+ allow(input).to receive(:gets).and_return("stop\n", "exit\n")
157
187
  end
158
188
 
159
189
  it "unsets the debug flag" do
160
- expect(store).to have_received(:debug=).with(false)
190
+ expect(store).to receive(:debug=).with(false)
191
+
192
+ subject.run
161
193
  end
162
194
 
163
195
  it "updates logging status" do
164
- expect(Imap::Backup).to have_received(:setup_logging).twice
196
+ expect(Imap::Backup).to receive(:setup_logging).twice
197
+
198
+ subject.run
165
199
  end
166
200
  end
167
201
  end
@@ -169,43 +203,50 @@ describe Imap::Backup::Configuration::Setup do
169
203
 
170
204
  context "when 'save' is selected" do
171
205
  before do
172
- allow(@input).to receive(:gets).and_return("save\n")
173
- subject.run
206
+ allow(input).to receive(:gets) { "save\n" }
174
207
  end
175
208
 
176
209
  it "exits" do
177
210
  # N.B. this will hang forever if save does not cause an exit
211
+ subject.run
178
212
  end
179
213
 
180
214
  it "saves the configuration" do
181
- expect(store).to have_received(:save)
215
+ expect(store).to receive(:save)
216
+
217
+ subject.run
182
218
  end
183
219
  end
184
220
 
185
221
  context "when 'exit without saving' is selected" do
186
222
  before do
187
- allow(@input).to receive(:gets).and_return("exit\n")
188
-
189
- subject.run
223
+ allow(input).to receive(:gets) { "exit\n" }
190
224
  end
191
225
 
192
226
  it "exits" do
193
227
  # N.B. this will hang forever if quit does not cause an exit
228
+ subject.run
194
229
  end
195
230
 
196
231
  context "when the configuration is modified" do
197
232
  let(:modified) { true }
198
233
 
199
234
  it "doesn't save the configuration" do
200
- expect(store).to_not have_received(:save)
235
+ expect(store).to_not receive(:save)
236
+
237
+ subject.run
201
238
  end
202
239
  end
203
240
 
204
241
  context "when the configuration isn't modified" do
205
242
  it "doesn't save the configuration" do
206
- expect(store).to_not have_received(:save)
243
+ expect(store).to_not receive(:save)
244
+
245
+ subject.run
207
246
  end
208
247
  end
209
248
  end
210
249
  end
211
250
  end
251
+
252
+ # rubocop:enable RSpec/NestedGroups
@@ -1,6 +1,7 @@
1
- require "spec_helper"
2
1
  require "json"
3
2
 
3
+ # rubocop:disable RSpec/PredicateMatcher
4
+
4
5
  describe Imap::Backup::Configuration::Store do
5
6
  let(:directory) { "/base/path" }
6
7
  let(:file_path) { File.join(directory, "/config.json") }
@@ -19,11 +20,11 @@ describe Imap::Backup::Configuration::Store do
19
20
  allow(File).to receive(:exist?).and_call_original
20
21
  allow(File).to receive(:exist?).with(file_path) { file_exists }
21
22
  allow(Imap::Backup::Utils).
22
- to receive(:stat).with(directory).and_return(0o700)
23
+ to receive(:stat).with(directory) { 0o700 }
23
24
  allow(Imap::Backup::Utils).
24
- to receive(:stat).with(file_path).and_return(0o600)
25
- allow(Imap::Backup::Utils).to receive(:check_permissions).and_return(nil)
26
- allow(File).to receive(:read).with(file_path).and_return(configuration)
25
+ to receive(:stat).with(file_path) { 0o600 }
26
+ allow(Imap::Backup::Utils).to receive(:check_permissions) { nil }
27
+ allow(File).to receive(:read).with(file_path) { configuration }
27
28
  end
28
29
 
29
30
  describe ".exist?" do
@@ -46,7 +47,7 @@ describe Imap::Backup::Configuration::Store do
46
47
  end
47
48
 
48
49
  describe "#modified?" do
49
- context "'with accounts flagged 'modified'" do
50
+ context "with accounts flagged 'modified'" do
50
51
  let(:accounts) { [{name: "foo", modified: true}] }
51
52
 
52
53
  it "is true" do
@@ -54,7 +55,7 @@ describe Imap::Backup::Configuration::Store do
54
55
  end
55
56
  end
56
57
 
57
- context "'with accounts flagged 'delete'" do
58
+ context "with accounts flagged 'delete'" do
58
59
  let(:accounts) { [{name: "foo", delete: true}] }
59
60
 
60
61
  it "is true" do
@@ -130,28 +131,28 @@ describe Imap::Backup::Configuration::Store do
130
131
  end
131
132
 
132
133
  describe "#save" do
134
+ subject { described_class.new }
135
+
133
136
  let(:directory_exists) { false }
134
- let(:file) { double("File", write: nil) }
137
+ let(:file) { instance_double(File, write: nil) }
135
138
 
136
139
  before do
137
140
  allow(FileUtils).to receive(:mkdir)
138
141
  allow(FileUtils).to receive(:chmod)
139
- allow(File).to receive(:open).with(file_path, "w") { |&b| b.call file }
140
- allow(JSON).to receive(:pretty_generate).and_return("JSON output")
142
+ allow(File).to receive(:open).with(file_path, "w").and_yield(file)
143
+ allow(JSON).to receive(:pretty_generate) { "JSON output" }
141
144
  end
142
145
 
143
- subject { described_class.new }
144
-
145
146
  it "creates the config directory" do
146
- subject.save
147
+ expect(FileUtils).to receive(:mkdir).with(directory)
147
148
 
148
- expect(FileUtils).to have_received(:mkdir).with(directory)
149
+ subject.save
149
150
  end
150
151
 
151
152
  it "saves the configuration" do
152
- subject.save
153
+ expect(file).to receive(:write).with("JSON output")
153
154
 
154
- expect(file).to have_received(:write).with("JSON output")
155
+ subject.save
155
156
  end
156
157
 
157
158
  context "when accounts are modified" do
@@ -163,7 +164,9 @@ describe Imap::Backup::Configuration::Store do
163
164
  expected = Marshal.load(Marshal.dump(data))
164
165
  expected[:accounts][0].delete(:modified)
165
166
 
166
- expect(JSON).to have_received(:pretty_generate).with(expected)
167
+ expect(JSON).to receive(:pretty_generate).with(expected)
168
+
169
+ subject.save
167
170
  end
168
171
  end
169
172
 
@@ -175,25 +178,25 @@ describe Imap::Backup::Configuration::Store do
175
178
  ]
176
179
  end
177
180
 
178
- before { subject.save }
179
-
180
181
  it "does not save them" do
181
182
  expected = Marshal.load(Marshal.dump(data))
182
183
  expected[:accounts].pop
183
184
 
184
- expect(JSON).to have_received(:pretty_generate).with(expected)
185
+ expect(JSON).to receive(:pretty_generate).with(expected)
186
+
187
+ subject.save
185
188
  end
186
189
  end
187
190
 
188
191
  context "when file permissions are too open" do
189
- before { subject.save }
190
-
191
192
  it "sets them to 0600" do
192
- expect(FileUtils).to have_received(:chmod).with(0o600, file_path)
193
+ expect(FileUtils).to receive(:chmod).with(0o600, file_path)
194
+
195
+ subject.save
193
196
  end
194
197
  end
195
198
 
196
- context "if the configuration file is missing" do
199
+ context "when the configuration file is missing" do
197
200
  let(:file_exists) { false }
198
201
 
199
202
  it "doesn't fail" do
@@ -203,7 +206,7 @@ describe Imap::Backup::Configuration::Store do
203
206
  end
204
207
  end
205
208
 
206
- context "if the config file permissions are too lax" do
209
+ context "when the config file permissions are too lax" do
207
210
  let(:file_exists) { true }
208
211
 
209
212
  before do
@@ -219,3 +222,5 @@ describe Imap::Backup::Configuration::Store do
219
222
  end
220
223
  end
221
224
  end
225
+
226
+ # rubocop:enable RSpec/PredicateMatcher
@@ -1,41 +1,43 @@
1
- require "spec_helper"
2
-
3
1
  describe Imap::Backup::Downloader do
4
2
  describe "#run" do
3
+ subject { described_class.new(folder, serializer) }
4
+
5
5
  let(:message) { {"RFC822" => "blah"} }
6
6
  let(:folder) do
7
- double("Imap::Backup::Account::Folder", fetch: message, name: "folder")
7
+ instance_double(
8
+ Imap::Backup::Account::Folder,
9
+ fetch: message,
10
+ name: "folder",
11
+ uids: folder_uids
12
+ )
13
+ end
14
+ let(:folder_uids) { %w(111 222 333) }
15
+ let(:serializer) do
16
+ instance_double(Imap::Backup::Serializer::Mbox, save: nil, uids: ["222"])
8
17
  end
9
- let(:folder_uids) { ["111", "222", "333"] }
10
- let(:serializer) { double("Imap::Backup::Serializer", save: nil) }
11
- let(:serializer_uids) { ["222"] }
12
18
 
13
- subject { described_class.new(folder, serializer) }
19
+ context "with fetched messages" do
20
+ specify "are saved" do
21
+ expect(serializer).to receive(:save).with("111", message)
14
22
 
15
- before do
16
- allow(folder).to receive(:uids).and_return(folder_uids)
17
- allow(serializer).to receive(:uids).and_return(serializer_uids)
18
- allow(folder).to receive(:fetch).with("333").and_return(nil)
19
- subject.run
23
+ subject.run
24
+ end
20
25
  end
21
26
 
22
- context "#run" do
23
- context "fetched messages" do
24
- it "are saved" do
25
- expect(serializer).to have_received(:save).with("111", message)
26
- end
27
- end
27
+ context "with messages which are already present" do
28
+ specify "are skipped" do
29
+ expect(serializer).to_not receive(:save).with("222", anything)
28
30
 
29
- context "messages which are already present" do
30
- specify "are skipped" do
31
- expect(serializer).to_not have_received(:save).with("222", anything)
32
- end
31
+ subject.run
33
32
  end
33
+ end
34
+
35
+ context "with failed fetches" do
36
+ specify "are skipped" do
37
+ allow(folder).to receive(:fetch).with("333") { nil }
38
+ expect(serializer).to_not receive(:save).with("333", anything)
34
39
 
35
- context "failed fetches" do
36
- specify "are skipped" do
37
- expect(serializer).to_not have_received(:save).with("333", anything)
38
- end
40
+ subject.run
39
41
  end
40
42
  end
41
43
  end