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.
- checksums.yaml +4 -4
- data/.rspec +0 -1
- data/.rspec-all +2 -0
- data/.rubocop.yml +15 -2
- data/.rubocop_todo.yml +58 -0
- data/.travis.yml +15 -2
- data/README.md +14 -22
- data/Rakefile +6 -3
- data/bin/imap-backup +5 -11
- data/imap-backup.gemspec +10 -6
- data/lib/email/mboxrd/message.rb +16 -16
- data/lib/imap/backup/account/connection.rb +38 -22
- data/lib/imap/backup/account/folder.rb +23 -7
- data/lib/imap/backup/configuration/account.rb +25 -21
- data/lib/imap/backup/configuration/asker.rb +3 -2
- data/lib/imap/backup/configuration/connection_tester.rb +1 -1
- data/lib/imap/backup/configuration/folder_chooser.rb +32 -5
- data/lib/imap/backup/configuration/list.rb +2 -0
- data/lib/imap/backup/configuration/setup.rb +2 -1
- data/lib/imap/backup/configuration/store.rb +3 -6
- data/lib/imap/backup/downloader.rb +8 -7
- data/lib/imap/backup/serializer/mbox.rb +44 -25
- data/lib/imap/backup/serializer/mbox_enumerator.rb +31 -0
- data/lib/imap/backup/serializer/mbox_store.rb +35 -32
- data/lib/imap/backup/uploader.rb +11 -2
- data/lib/imap/backup/utils.rb +11 -9
- data/lib/imap/backup/version.rb +2 -2
- data/spec/features/backup_spec.rb +6 -5
- data/spec/features/helper.rb +1 -1
- data/spec/features/restore_spec.rb +75 -27
- data/spec/features/support/backup_directory.rb +7 -7
- data/spec/features/support/email_server.rb +15 -11
- data/spec/features/support/shared/connection_context.rb +2 -2
- data/spec/features/support/shared/message_fixtures.rb +8 -0
- data/spec/spec_helper.rb +1 -1
- data/spec/support/fixtures.rb +2 -2
- data/spec/support/higline_test_helpers.rb +1 -1
- data/spec/unit/email/mboxrd/message_spec.rb +73 -53
- data/spec/unit/email/provider_spec.rb +3 -5
- data/spec/unit/imap/backup/account/connection_spec.rb +82 -59
- data/spec/unit/imap/backup/account/folder_spec.rb +75 -37
- data/spec/unit/imap/backup/configuration/account_spec.rb +95 -61
- data/spec/unit/imap/backup/configuration/asker_spec.rb +43 -45
- data/spec/unit/imap/backup/configuration/connection_tester_spec.rb +21 -22
- data/spec/unit/imap/backup/configuration/folder_chooser_spec.rb +66 -33
- data/spec/unit/imap/backup/configuration/list_spec.rb +32 -11
- data/spec/unit/imap/backup/configuration/setup_spec.rb +97 -56
- data/spec/unit/imap/backup/configuration/store_spec.rb +30 -25
- data/spec/unit/imap/backup/downloader_spec.rb +28 -26
- data/spec/unit/imap/backup/serializer/mbox_enumerator_spec.rb +45 -0
- data/spec/unit/imap/backup/serializer/mbox_spec.rb +109 -51
- data/spec/unit/imap/backup/serializer/mbox_store_spec.rb +232 -20
- data/spec/unit/imap/backup/uploader_spec.rb +23 -9
- data/spec/unit/imap/backup/utils_spec.rb +14 -15
- data/spec/unit/imap/backup_spec.rb +28 -0
- metadata +13 -7
@@ -1,9 +1,9 @@
|
|
1
|
-
|
1
|
+
# rubocop:disable RSpec/NestedGroups
|
2
2
|
|
3
3
|
describe Imap::Backup::Configuration::Setup do
|
4
4
|
include HighLineTestHelpers
|
5
5
|
|
6
|
-
|
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
|
-
|
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
|
-
|
19
|
-
|
20
|
-
:
|
21
|
-
:
|
22
|
-
:
|
23
|
-
|
24
|
-
|
25
|
-
|
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
|
36
|
+
before do
|
32
37
|
allow(Imap::Backup::Configuration::Store).to receive(:new) { store }
|
33
38
|
allow(Imap::Backup).to receive(:setup_logging)
|
34
|
-
|
35
|
-
allow(
|
36
|
-
allow(
|
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
|
-
|
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(
|
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
|
-
|
55
|
+
expect(Kernel).to receive(:system).with("clear")
|
54
56
|
|
55
|
-
|
57
|
+
subject.run
|
56
58
|
end
|
57
59
|
|
58
60
|
it "updates logging status" do
|
59
|
-
|
61
|
+
expect(Imap::Backup).to receive(:setup_logging)
|
60
62
|
|
61
|
-
|
63
|
+
subject.run
|
62
64
|
end
|
63
65
|
|
64
|
-
|
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
|
-
|
73
|
+
describe "normal accounts" do
|
72
74
|
it "are listed" do
|
73
|
-
expect(
|
75
|
+
expect(output.string).to match(/account@example.com/)
|
74
76
|
end
|
75
77
|
end
|
76
78
|
|
77
|
-
|
79
|
+
describe "modified accounts" do
|
78
80
|
it "are flagged" do
|
79
|
-
expect(
|
81
|
+
expect(output.string).to match(/modified@example.com \*/)
|
80
82
|
end
|
81
83
|
end
|
82
84
|
|
83
|
-
|
85
|
+
describe "deleted accounts" do
|
84
86
|
it "are hidden" do
|
85
|
-
expect(
|
87
|
+
expect(output.string).to_not match(/delete@example.com/)
|
86
88
|
end
|
87
89
|
end
|
88
90
|
end
|
89
91
|
|
90
|
-
context "
|
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)
|
121
|
+
let(:account) do
|
122
|
+
instance_double(Imap::Backup::Configuration::Account, run: nil)
|
123
|
+
end
|
100
124
|
|
101
125
|
before do
|
102
|
-
allow(
|
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)
|
128
|
+
with(no_args) { "new@example.com" }
|
105
129
|
allow(Imap::Backup::Configuration::Account).to receive(:new).
|
106
|
-
with(store, blank_account, anything)
|
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
|
-
|
144
|
+
describe "logging" do
|
121
145
|
context "when debug logging is disabled" do
|
122
146
|
before do
|
123
|
-
allow(
|
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
|
-
|
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
|
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
|
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(
|
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
|
-
|
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(
|
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
|
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
|
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(
|
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
|
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(
|
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
|
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
|
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)
|
23
|
+
to receive(:stat).with(directory) { 0o700 }
|
23
24
|
allow(Imap::Backup::Utils).
|
24
|
-
to receive(:stat).with(file_path)
|
25
|
-
allow(Imap::Backup::Utils).to receive(:check_permissions)
|
26
|
-
allow(File).to receive(:read).with(file_path)
|
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 "
|
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 "
|
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) {
|
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")
|
140
|
-
allow(JSON).to receive(:pretty_generate)
|
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
|
-
|
147
|
+
expect(FileUtils).to receive(:mkdir).with(directory)
|
147
148
|
|
148
|
-
|
149
|
+
subject.save
|
149
150
|
end
|
150
151
|
|
151
152
|
it "saves the configuration" do
|
152
|
-
|
153
|
+
expect(file).to receive(:write).with("JSON output")
|
153
154
|
|
154
|
-
|
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
|
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
|
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
|
193
|
+
expect(FileUtils).to receive(:chmod).with(0o600, file_path)
|
194
|
+
|
195
|
+
subject.save
|
193
196
|
end
|
194
197
|
end
|
195
198
|
|
196
|
-
context "
|
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 "
|
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
|
-
|
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
|
-
|
19
|
+
context "with fetched messages" do
|
20
|
+
specify "are saved" do
|
21
|
+
expect(serializer).to receive(:save).with("111", message)
|
14
22
|
|
15
|
-
|
16
|
-
|
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 "
|
23
|
-
|
24
|
-
|
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
|
-
|
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
|
-
|
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
|