imap-backup 2.1.1 → 3.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.rubocop.yml +5 -4
- data/.rubocop_todo.yml +29 -11
- data/.travis.yml +1 -1
- data/README.md +10 -13
- data/bin/imap-backup +5 -2
- data/docs/01-credentials-screen.png +0 -0
- data/docs/02-new-project.png +0 -0
- data/docs/03-initial-credentials-for-project.png +0 -0
- data/docs/04-credential-type-selection.png +0 -0
- data/docs/05-cant-create-without-consent-setup.png +0 -0
- data/docs/06-user-type-selection.png +0 -0
- data/docs/07-consent-screen-form.png +0 -0
- data/docs/08-app-scopes.png +0 -0
- data/docs/09-scope-selection.png +0 -0
- data/docs/10-updated-app-scopes.png +0 -0
- data/docs/11-test-users.png +0 -0
- data/docs/12-add-users.png +0 -0
- data/docs/13-create-oauth-client.png +0 -0
- data/docs/14-application-details.png +0 -0
- data/docs/16-initial-menu.png +0 -0
- data/docs/17-inputting-the-email-address.png +0 -0
- data/docs/18-choose-password.png +0 -0
- data/docs/19-supply-client-info.png +0 -0
- data/docs/20-choose-gmail-account.png +0 -0
- data/docs/21-accept-warnings.png +0 -0
- data/docs/22-grant-access.png +0 -0
- data/docs/24-confirm-choices.png +0 -0
- data/docs/25-success-code.png +0 -0
- data/docs/26-type-code-into-imap-backup.png +0 -0
- data/docs/27-success.png +0 -0
- data/docs/setting-up-gmail.md +166 -0
- data/imap-backup.gemspec +3 -9
- data/lib/email/mboxrd/message.rb +4 -3
- data/lib/email/provider.rb +3 -1
- data/lib/gmail/authenticator.rb +160 -0
- data/lib/google/auth/stores/in_memory_token_store.rb +9 -0
- data/lib/imap/backup.rb +2 -1
- data/lib/imap/backup/account/connection.rb +59 -34
- data/lib/imap/backup/account/folder.rb +10 -1
- data/lib/imap/backup/configuration/account.rb +9 -1
- data/lib/imap/backup/configuration/gmail_oauth2.rb +82 -0
- data/lib/imap/backup/configuration/setup.rb +4 -1
- data/lib/imap/backup/serializer/mbox.rb +4 -0
- data/lib/imap/backup/serializer/mbox_enumerator.rb +1 -1
- data/lib/imap/backup/serializer/mbox_store.rb +20 -4
- data/lib/imap/backup/uploader.rb +10 -2
- data/lib/imap/backup/version.rb +5 -4
- data/spec/features/backup_spec.rb +3 -3
- data/spec/features/helper.rb +1 -1
- data/spec/features/restore_spec.rb +75 -27
- data/spec/features/support/backup_directory.rb +2 -2
- data/spec/features/support/email_server.rb +1 -3
- data/spec/features/support/shared/message_fixtures.rb +8 -0
- data/spec/spec_helper.rb +1 -1
- data/spec/support/fixtures.rb +1 -1
- data/spec/unit/email/mboxrd/message_spec.rb +2 -8
- data/spec/unit/email/provider_spec.rb +2 -2
- data/spec/unit/gmail/authenticator_spec.rb +138 -0
- data/spec/unit/google/auth/stores/in_memory_token_store_spec.rb +15 -0
- data/spec/unit/imap/backup/account/connection_spec.rb +157 -79
- data/spec/unit/imap/backup/account/folder_spec.rb +30 -20
- data/spec/unit/imap/backup/configuration/account_spec.rb +65 -46
- data/spec/unit/imap/backup/configuration/asker_spec.rb +20 -17
- data/spec/unit/imap/backup/configuration/connection_tester_spec.rb +6 -10
- data/spec/unit/imap/backup/configuration/folder_chooser_spec.rb +16 -10
- data/spec/unit/imap/backup/configuration/gmail_oauth2_spec.rb +84 -0
- data/spec/unit/imap/backup/configuration/list_spec.rb +6 -3
- data/spec/unit/imap/backup/configuration/setup_spec.rb +89 -54
- data/spec/unit/imap/backup/configuration/store_spec.rb +18 -16
- data/spec/unit/imap/backup/downloader_spec.rb +14 -14
- data/spec/unit/imap/backup/serializer/mbox_enumerator_spec.rb +6 -1
- data/spec/unit/imap/backup/serializer/mbox_spec.rb +62 -40
- data/spec/unit/imap/backup/serializer/mbox_store_spec.rb +94 -35
- data/spec/unit/imap/backup/uploader_spec.rb +23 -7
- data/spec/unit/imap/backup/utils_spec.rb +10 -9
- metadata +68 -9
@@ -1,30 +1,16 @@
|
|
1
|
-
# rubocop:disable RSpec/NestedGroups
|
2
|
-
|
3
1
|
describe Imap::Backup::Configuration::Account do
|
4
|
-
|
5
|
-
|
6
|
-
|
2
|
+
ACCOUNT = "account".freeze
|
3
|
+
GMAIL_IMAP_SERVER = "imap.gmail.com".freeze
|
4
|
+
HIGHLINE = "highline".freeze
|
5
|
+
STORE = "store".freeze
|
7
6
|
|
8
|
-
|
9
|
-
@choices = {}
|
10
|
-
end
|
7
|
+
subject { described_class.new(store, account, highline) }
|
11
8
|
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
def hidden(name, &block)
|
17
|
-
choices[name] = block
|
18
|
-
end
|
19
|
-
end
|
9
|
+
let(:account) { ACCOUNT }
|
10
|
+
let(:highline) { HIGHLINE }
|
11
|
+
let(:store) { STORE }
|
20
12
|
|
21
13
|
describe "#initialize" do
|
22
|
-
subject { described_class.new(store, account, highline) }
|
23
|
-
|
24
|
-
let(:store) { "store" }
|
25
|
-
let(:account) { "account" }
|
26
|
-
let(:highline) { "highline" }
|
27
|
-
|
28
14
|
[:store, :account, :highline].each do |param|
|
29
15
|
it "expects #{param}" do
|
30
16
|
expect(subject.send(param)).to eq(send(param))
|
@@ -33,10 +19,27 @@ describe Imap::Backup::Configuration::Account do
|
|
33
19
|
end
|
34
20
|
|
35
21
|
describe "#run" do
|
36
|
-
|
22
|
+
let(:highline_menu_class) do
|
23
|
+
Class.new do
|
24
|
+
attr_reader :choices
|
25
|
+
attr_accessor :header
|
26
|
+
|
27
|
+
def initialize
|
28
|
+
@choices = {}
|
29
|
+
end
|
30
|
+
|
31
|
+
def choice(name, &block)
|
32
|
+
choices[name] = block
|
33
|
+
end
|
34
|
+
|
35
|
+
def hidden(name, &block)
|
36
|
+
choices[name] = block
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
37
40
|
|
38
41
|
let(:highline) { instance_double(HighLine) }
|
39
|
-
let(:menu) {
|
42
|
+
let(:menu) { highline_menu_class.new }
|
40
43
|
let(:store) do
|
41
44
|
instance_double(Imap::Backup::Configuration::Store, accounts: accounts)
|
42
45
|
end
|
@@ -44,7 +47,7 @@ describe Imap::Backup::Configuration::Account do
|
|
44
47
|
let(:account) do
|
45
48
|
{
|
46
49
|
username: existing_email,
|
47
|
-
server:
|
50
|
+
server: current_server,
|
48
51
|
local_path: "/backup/path",
|
49
52
|
folders: [{name: "my_folder"}],
|
50
53
|
password: existing_password
|
@@ -58,7 +61,7 @@ describe Imap::Backup::Configuration::Account do
|
|
58
61
|
end
|
59
62
|
let(:existing_email) { "user@example.com" }
|
60
63
|
let(:new_email) { "foo@example.com" }
|
61
|
-
let(:
|
64
|
+
let(:current_server) { "imap.example.com" }
|
62
65
|
let(:existing_password) { "password" }
|
63
66
|
let(:other_email) { "other@example.com" }
|
64
67
|
let(:other_existing_path) { "/other/existing/path" }
|
@@ -73,15 +76,17 @@ describe Imap::Backup::Configuration::Account do
|
|
73
76
|
end
|
74
77
|
|
75
78
|
describe "preparation" do
|
76
|
-
before { subject.run }
|
77
|
-
|
78
79
|
it "clears the screen" do
|
79
|
-
expect(Kernel).to
|
80
|
+
expect(Kernel).to receive(:system).with("clear")
|
81
|
+
|
82
|
+
subject.run
|
80
83
|
end
|
81
84
|
|
82
85
|
describe "menu" do
|
83
86
|
it "shows the menu" do
|
84
|
-
expect(highline).to
|
87
|
+
expect(highline).to receive(:choose)
|
88
|
+
|
89
|
+
subject.run
|
85
90
|
end
|
86
91
|
end
|
87
92
|
end
|
@@ -132,7 +137,7 @@ describe Imap::Backup::Configuration::Account do
|
|
132
137
|
end
|
133
138
|
end
|
134
139
|
|
135
|
-
describe "email" do
|
140
|
+
describe "choosing 'modify email'" do
|
136
141
|
before do
|
137
142
|
allow(Imap::Backup::Configuration::Asker).
|
138
143
|
to receive(:email) { new_email }
|
@@ -142,7 +147,7 @@ describe Imap::Backup::Configuration::Account do
|
|
142
147
|
|
143
148
|
context "when the server is blank" do
|
144
149
|
[
|
145
|
-
["GMail", "foo@gmail.com",
|
150
|
+
["GMail", "foo@gmail.com", GMAIL_IMAP_SERVER],
|
146
151
|
["Fastmail", "bar@fastmail.fm", "imap.fastmail.com"],
|
147
152
|
["Fastmail", "bar@fastmail.com", "imap.fastmail.com"]
|
148
153
|
].each do |service, email, expected|
|
@@ -150,7 +155,7 @@ describe Imap::Backup::Configuration::Account do
|
|
150
155
|
let(:new_email) { email }
|
151
156
|
|
152
157
|
context "with nil" do
|
153
|
-
let(:
|
158
|
+
let(:current_server) { nil }
|
154
159
|
|
155
160
|
it "sets a default server" do
|
156
161
|
expect(account[:server]).to eq(expected)
|
@@ -158,7 +163,7 @@ describe Imap::Backup::Configuration::Account do
|
|
158
163
|
end
|
159
164
|
|
160
165
|
context "with an empty string" do
|
161
|
-
let(:
|
166
|
+
let(:current_server) { "" }
|
162
167
|
|
163
168
|
it "sets a default server" do
|
164
169
|
expect(account[:server]).to eq(expected)
|
@@ -168,7 +173,7 @@ describe Imap::Backup::Configuration::Account do
|
|
168
173
|
end
|
169
174
|
|
170
175
|
context "when the domain is unrecognized" do
|
171
|
-
let(:
|
176
|
+
let(:current_server) { nil }
|
172
177
|
let(:provider) do
|
173
178
|
instance_double(Email::Provider, provider: :default)
|
174
179
|
end
|
@@ -207,12 +212,18 @@ describe Imap::Backup::Configuration::Account do
|
|
207
212
|
end
|
208
213
|
end
|
209
214
|
|
210
|
-
describe "password" do
|
215
|
+
describe "choosing 'modify password'" do
|
211
216
|
let(:new_password) { "new_password" }
|
217
|
+
let(:gmail_oauth2) do
|
218
|
+
instance_double(Imap::Backup::Configuration::GmailOauth2, run: nil)
|
219
|
+
end
|
212
220
|
|
213
221
|
before do
|
214
222
|
allow(Imap::Backup::Configuration::Asker).
|
215
223
|
to receive(:password) { new_password }
|
224
|
+
allow(Imap::Backup::Configuration::GmailOauth2).
|
225
|
+
to receive(:new).
|
226
|
+
with(account) { gmail_oauth2 }
|
216
227
|
subject.run
|
217
228
|
menu.choices["modify password"].call
|
218
229
|
end
|
@@ -234,14 +245,24 @@ describe Imap::Backup::Configuration::Account do
|
|
234
245
|
|
235
246
|
include_examples "it doesn't flag the account as modified"
|
236
247
|
end
|
248
|
+
|
249
|
+
context "when the server is for GMail" do
|
250
|
+
let(:current_server) { GMAIL_IMAP_SERVER }
|
251
|
+
|
252
|
+
it "sets up GMail OAuth2" do
|
253
|
+
expect(gmail_oauth2).to have_received(:run)
|
254
|
+
end
|
255
|
+
end
|
237
256
|
end
|
238
257
|
|
239
|
-
describe "server" do
|
258
|
+
describe "choosing 'modify server'" do
|
240
259
|
let(:server) { "server" }
|
241
260
|
|
242
261
|
before do
|
243
|
-
allow(highline).to receive(:ask).with("server: ")
|
262
|
+
allow(highline).to receive(:ask).with("server: ") { server }
|
263
|
+
|
244
264
|
subject.run
|
265
|
+
|
245
266
|
menu.choices["modify server"].call
|
246
267
|
end
|
247
268
|
|
@@ -252,7 +273,7 @@ describe Imap::Backup::Configuration::Account do
|
|
252
273
|
include_examples "it flags the account as modified"
|
253
274
|
end
|
254
275
|
|
255
|
-
describe "
|
276
|
+
describe "choosing 'modify backup path'" do
|
256
277
|
let(:new_backup_path) { "/new/path" }
|
257
278
|
|
258
279
|
before do
|
@@ -290,7 +311,7 @@ describe Imap::Backup::Configuration::Account do
|
|
290
311
|
include_examples "it flags the account as modified"
|
291
312
|
end
|
292
313
|
|
293
|
-
describe "folders" do
|
314
|
+
describe "choosing 'choose backup folders'" do
|
294
315
|
let(:chooser) do
|
295
316
|
instance_double(Imap::Backup::Configuration::FolderChooser, run: nil)
|
296
317
|
end
|
@@ -307,10 +328,10 @@ describe Imap::Backup::Configuration::Account do
|
|
307
328
|
end
|
308
329
|
end
|
309
330
|
|
310
|
-
describe "
|
331
|
+
describe "choosing 'test connection'" do
|
311
332
|
before do
|
312
333
|
allow(Imap::Backup::Configuration::ConnectionTester).
|
313
|
-
to receive(:test)
|
334
|
+
to receive(:test) { "All fine" }
|
314
335
|
allow(highline).to receive(:ask)
|
315
336
|
subject.run
|
316
337
|
menu.choices["test connection"].call
|
@@ -322,11 +343,11 @@ describe Imap::Backup::Configuration::Account do
|
|
322
343
|
end
|
323
344
|
end
|
324
345
|
|
325
|
-
describe "
|
346
|
+
describe "choosing 'delete'" do
|
326
347
|
let(:confirmed) { true }
|
327
348
|
|
328
349
|
before do
|
329
|
-
allow(highline).to receive(:agree)
|
350
|
+
allow(highline).to receive(:agree) { confirmed }
|
330
351
|
subject.run
|
331
352
|
catch :done do
|
332
353
|
menu.choices["delete"].call
|
@@ -349,5 +370,3 @@ describe Imap::Backup::Configuration::Account do
|
|
349
370
|
end
|
350
371
|
end
|
351
372
|
end
|
352
|
-
|
353
|
-
# rubocop:enable RSpec/NestedGroups
|
@@ -30,9 +30,9 @@ module Imap::Backup
|
|
30
30
|
].each do |method, params, prompt|
|
31
31
|
context ".#{method}" do
|
32
32
|
it "asks for input" do
|
33
|
-
|
33
|
+
expect(highline).to receive(:ask).with("#{prompt}: ")
|
34
34
|
|
35
|
-
|
35
|
+
described_class.send(method, *params)
|
36
36
|
end
|
37
37
|
|
38
38
|
it "returns the answer" do
|
@@ -56,16 +56,15 @@ module Imap::Backup
|
|
56
56
|
describe "#email" do
|
57
57
|
let(:email) { "email@example.com" }
|
58
58
|
let(:answer) { email }
|
59
|
-
let(:result) { subject.email }
|
60
|
-
|
61
|
-
before { result }
|
62
59
|
|
63
60
|
it "asks for an email" do
|
64
|
-
expect(highline).to
|
61
|
+
expect(highline).to receive(:ask).with(/email/)
|
62
|
+
|
63
|
+
subject.email
|
65
64
|
end
|
66
65
|
|
67
66
|
it "returns the address" do
|
68
|
-
expect(
|
67
|
+
expect(subject.email).to eq(email)
|
69
68
|
end
|
70
69
|
end
|
71
70
|
|
@@ -73,7 +72,6 @@ module Imap::Backup
|
|
73
72
|
let(:password1) { "password" }
|
74
73
|
let(:password2) { "password" }
|
75
74
|
let(:answers) { [true, false] }
|
76
|
-
let(:result) { subject.password }
|
77
75
|
|
78
76
|
before do
|
79
77
|
i = 0
|
@@ -84,27 +82,32 @@ module Imap::Backup
|
|
84
82
|
i += 1
|
85
83
|
answer
|
86
84
|
end
|
87
|
-
result
|
88
85
|
end
|
89
86
|
|
90
87
|
it "asks for a password" do
|
91
|
-
expect(highline).to
|
88
|
+
expect(highline).to receive(:ask).with("password: ")
|
89
|
+
|
90
|
+
subject.password
|
92
91
|
end
|
93
92
|
|
94
93
|
it "asks for confirmation" do
|
95
|
-
expect(highline).to
|
94
|
+
expect(highline).to receive(:ask).with("repeat password: ")
|
95
|
+
|
96
|
+
subject.password
|
96
97
|
end
|
97
98
|
|
98
99
|
it "returns the password" do
|
99
|
-
expect(
|
100
|
+
expect(subject.password).to eq(password1)
|
100
101
|
end
|
101
102
|
|
102
103
|
context "with different answers" do
|
103
104
|
let(:password2) { "secret" }
|
104
105
|
|
105
106
|
it "asks to continue" do
|
106
|
-
expect(highline).to
|
107
|
+
expect(highline).to receive(:agree).
|
107
108
|
at_least(:once).with(/Continue\?/)
|
109
|
+
|
110
|
+
subject.password
|
108
111
|
end
|
109
112
|
end
|
110
113
|
end
|
@@ -112,22 +115,22 @@ module Imap::Backup
|
|
112
115
|
describe "#backup_path" do
|
113
116
|
let(:path) { "/path" }
|
114
117
|
let(:answer) { path }
|
115
|
-
let(:result) { subject.backup_path("", //) }
|
116
118
|
|
117
119
|
before do
|
118
120
|
allow(highline).to receive(:ask) do |&b|
|
119
121
|
b.call query
|
120
122
|
path
|
121
123
|
end
|
122
|
-
result
|
123
124
|
end
|
124
125
|
|
125
126
|
it "asks for a directory" do
|
126
|
-
expect(highline).to
|
127
|
+
expect(highline).to receive(:ask).with(/directory/)
|
128
|
+
|
129
|
+
subject.backup_path("", //)
|
127
130
|
end
|
128
131
|
|
129
132
|
it "returns the path" do
|
130
|
-
expect(
|
133
|
+
expect(subject.backup_path("", //)).to eq(path)
|
131
134
|
end
|
132
135
|
end
|
133
136
|
end
|
@@ -3,32 +3,28 @@ describe Imap::Backup::Configuration::ConnectionTester do
|
|
3
3
|
let(:connection) do
|
4
4
|
instance_double(Imap::Backup::Account::Connection, imap: nil)
|
5
5
|
end
|
6
|
-
let(:result) { subject.test("foo") }
|
7
6
|
|
8
7
|
before do
|
9
8
|
allow(Imap::Backup::Account::Connection).to receive(:new) { connection }
|
10
9
|
end
|
11
10
|
|
12
11
|
describe "call" do
|
13
|
-
before { result }
|
14
|
-
|
15
12
|
it "tries to connect" do
|
16
|
-
expect(connection).to
|
13
|
+
expect(connection).to receive(:imap)
|
14
|
+
|
15
|
+
subject.test("foo")
|
17
16
|
end
|
18
17
|
end
|
19
18
|
|
20
19
|
describe "success" do
|
21
|
-
before { result }
|
22
|
-
|
23
20
|
it "returns success" do
|
24
|
-
expect(
|
21
|
+
expect(subject.test("foo")).to match(/successful/)
|
25
22
|
end
|
26
23
|
end
|
27
24
|
|
28
25
|
describe "failure" do
|
29
26
|
before do
|
30
27
|
allow(connection).to receive(:imap).and_raise(error)
|
31
|
-
result
|
32
28
|
end
|
33
29
|
|
34
30
|
context "with no connection" do
|
@@ -39,7 +35,7 @@ describe Imap::Backup::Configuration::ConnectionTester do
|
|
39
35
|
end
|
40
36
|
|
41
37
|
it "returns error" do
|
42
|
-
expect(
|
38
|
+
expect(subject.test("foo")).to match(/no response/i)
|
43
39
|
end
|
44
40
|
end
|
45
41
|
|
@@ -47,7 +43,7 @@ describe Imap::Backup::Configuration::ConnectionTester do
|
|
47
43
|
let(:error) { "Error" }
|
48
44
|
|
49
45
|
it "returns error" do
|
50
|
-
expect(
|
46
|
+
expect(subject.test("foo")).to match(/unexpected error/i)
|
51
47
|
end
|
52
48
|
end
|
53
49
|
end
|
@@ -22,13 +22,15 @@ describe Imap::Backup::Configuration::FolderChooser do
|
|
22
22
|
end
|
23
23
|
|
24
24
|
describe "display" do
|
25
|
-
before { subject.run }
|
26
|
-
|
27
25
|
it "clears the screen" do
|
28
|
-
expect(Kernel).to
|
26
|
+
expect(Kernel).to receive(:system).with("clear")
|
27
|
+
|
28
|
+
subject.run
|
29
29
|
end
|
30
30
|
|
31
31
|
it "shows the menu" do
|
32
|
+
subject.run
|
33
|
+
|
32
34
|
expect(output.string).to match %r{Add/remove folders}
|
33
35
|
end
|
34
36
|
end
|
@@ -114,13 +116,14 @@ describe Imap::Backup::Configuration::FolderChooser do
|
|
114
116
|
|
115
117
|
before do
|
116
118
|
allow(Imap::Backup::Configuration::Setup.highline).
|
117
|
-
to receive(:ask)
|
118
|
-
subject.run
|
119
|
+
to receive(:ask) { "q" }
|
119
120
|
end
|
120
121
|
|
121
122
|
it "asks to press a key" do
|
122
123
|
expect(Imap::Backup::Configuration::Setup.highline).
|
123
|
-
to
|
124
|
+
to receive(:ask).with("Press a key ")
|
125
|
+
|
126
|
+
subject.run
|
124
127
|
end
|
125
128
|
end
|
126
129
|
|
@@ -129,18 +132,21 @@ describe Imap::Backup::Configuration::FolderChooser do
|
|
129
132
|
allow(Imap::Backup::Account::Connection).
|
130
133
|
to receive(:new).with(account).and_raise("error")
|
131
134
|
allow(Imap::Backup::Configuration::Setup.highline).
|
132
|
-
to receive(:ask)
|
133
|
-
subject.run
|
135
|
+
to receive(:ask) { "q" }
|
134
136
|
end
|
135
137
|
|
136
138
|
it "prints an error message" do
|
137
139
|
expect(Imap::Backup.logger).
|
138
|
-
to
|
140
|
+
to receive(:warn).with("Connection failed")
|
141
|
+
|
142
|
+
subject.run
|
139
143
|
end
|
140
144
|
|
141
145
|
it "asks to continue" do
|
142
146
|
expect(Imap::Backup::Configuration::Setup.highline).
|
143
|
-
to
|
147
|
+
to receive(:ask).with("Press a key ")
|
148
|
+
|
149
|
+
subject.run
|
144
150
|
end
|
145
151
|
end
|
146
152
|
end
|