imap-backup 2.0.0 → 2.2.2

Sign up to get free protection for your applications and to get access to all the features.
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