imap-backup 2.2.2 → 3.2.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (61) hide show
  1. checksums.yaml +4 -4
  2. data/.circleci/config.yml +51 -0
  3. data/.rubocop.yml +2 -2
  4. data/.rubocop_todo.yml +10 -26
  5. data/README.md +6 -13
  6. data/bin/imap-backup +4 -1
  7. data/docs/01-credentials-screen.png +0 -0
  8. data/docs/02-new-project.png +0 -0
  9. data/docs/03-initial-credentials-for-project.png +0 -0
  10. data/docs/04-credential-type-selection.png +0 -0
  11. data/docs/05-cant-create-without-consent-setup.png +0 -0
  12. data/docs/06-user-type-selection.png +0 -0
  13. data/docs/07-consent-screen-form.png +0 -0
  14. data/docs/08-app-scopes.png +0 -0
  15. data/docs/09-scope-selection.png +0 -0
  16. data/docs/10-updated-app-scopes.png +0 -0
  17. data/docs/11-test-users.png +0 -0
  18. data/docs/12-add-users.png +0 -0
  19. data/docs/13-create-oauth-client.png +0 -0
  20. data/docs/14-application-details.png +0 -0
  21. data/docs/16-initial-menu.png +0 -0
  22. data/docs/17-inputting-the-email-address.png +0 -0
  23. data/docs/18-choose-password.png +0 -0
  24. data/docs/19-supply-client-info.png +0 -0
  25. data/docs/20-choose-gmail-account.png +0 -0
  26. data/docs/21-accept-warnings.png +0 -0
  27. data/docs/22-grant-access.png +0 -0
  28. data/docs/24-confirm-choices.png +0 -0
  29. data/docs/25-success-code.png +0 -0
  30. data/docs/26-type-code-into-imap-backup.png +0 -0
  31. data/docs/27-success.png +0 -0
  32. data/docs/setting-up-gmail.md +166 -0
  33. data/imap-backup.gemspec +3 -8
  34. data/lib/email/mboxrd/message.rb +2 -0
  35. data/lib/email/provider.rb +3 -1
  36. data/lib/gmail/authenticator.rb +160 -0
  37. data/lib/google/auth/stores/in_memory_token_store.rb +9 -0
  38. data/lib/imap/backup.rb +2 -1
  39. data/lib/imap/backup/account/connection.rb +67 -35
  40. data/lib/imap/backup/account/folder.rb +18 -6
  41. data/lib/imap/backup/configuration/account.rb +9 -1
  42. data/lib/imap/backup/configuration/folder_chooser.rb +14 -15
  43. data/lib/imap/backup/configuration/gmail_oauth2.rb +82 -0
  44. data/lib/imap/backup/configuration/setup.rb +4 -1
  45. data/lib/imap/backup/configuration/store.rb +12 -12
  46. data/lib/imap/backup/serializer/mbox_store.rb +2 -2
  47. data/lib/imap/backup/version.rb +4 -3
  48. data/lib/retry_on_error.rb +14 -0
  49. data/spec/features/backup_spec.rb +1 -1
  50. data/spec/fixtures/connection.yml +1 -1
  51. data/spec/support/fixtures.rb +7 -2
  52. data/spec/unit/gmail/authenticator_spec.rb +138 -0
  53. data/spec/unit/google/auth/stores/in_memory_token_store_spec.rb +15 -0
  54. data/spec/unit/imap/backup/account/connection_spec.rb +146 -53
  55. data/spec/unit/imap/backup/account/folder_spec.rb +42 -10
  56. data/spec/unit/imap/backup/configuration/account_spec.rb +37 -24
  57. data/spec/unit/imap/backup/configuration/folder_chooser_spec.rb +7 -13
  58. data/spec/unit/imap/backup/configuration/gmail_oauth2_spec.rb +84 -0
  59. data/spec/unit/imap/backup/configuration/setup_spec.rb +51 -31
  60. metadata +83 -9
  61. data/.travis.yml +0 -25
@@ -1,50 +1,53 @@
1
- describe Imap::Backup::Account::Connection do
2
- def self.backup_folder
3
- "backup_folder"
4
- end
1
+ require "ostruct"
5
2
 
6
- def self.folder_config
7
- {name: backup_folder}
8
- end
3
+ describe Imap::Backup::Account::Connection do
4
+ BACKUP_FOLDER = "backup_folder".freeze
5
+ FOLDER_CONFIG = {name: BACKUP_FOLDER}.freeze
6
+ FOLDER_NAME = "my_folder".freeze
7
+ GMAIL_IMAP_SERVER = "imap.gmail.com".freeze
8
+ LOCAL_PATH = "local_path".freeze
9
+ LOCAL_UID = "local_uid".freeze
10
+ PASSWORD = "secret".freeze
11
+ ROOT_NAME = "foo".freeze
12
+ SERVER = "imap.example.com".freeze
13
+ USERNAME = "username@example.com".freeze
9
14
 
10
15
  subject { described_class.new(options) }
11
16
 
12
17
  let(:imap) do
13
- instance_double(Net::IMAP, login: nil, disconnect: nil)
18
+ instance_double(Net::IMAP, authenticate: nil, login: nil, disconnect: nil)
14
19
  end
15
20
  let(:imap_folders) { [] }
16
21
  let(:options) do
17
22
  {
18
- username: username,
19
- password: "password",
20
- local_path: local_path,
21
- folders: backup_folders
23
+ username: USERNAME,
24
+ password: PASSWORD,
25
+ local_path: LOCAL_PATH,
26
+ folders: config_folders,
27
+ server: server
22
28
  }
23
29
  end
24
- let(:local_path) { "local_path" }
25
- let(:backup_folders) { [self.class.folder_config] }
26
- let(:username) { "username@gmail.com" }
30
+ let(:config_folders) { [FOLDER_CONFIG] }
27
31
  let(:root_info) do
28
- instance_double(Net::IMAP::MailboxList, name: root_name)
32
+ instance_double(Net::IMAP::MailboxList, name: ROOT_NAME)
29
33
  end
30
- let(:root_name) { "foo" }
31
34
  let(:serializer) do
32
35
  instance_double(
33
36
  Imap::Backup::Serializer::Mbox,
34
37
  folder: serialized_folder,
35
38
  force_uid_validity: nil,
36
39
  apply_uid_validity: new_uid_validity,
37
- uids: [local_uid]
40
+ uids: [LOCAL_UID]
38
41
  )
39
42
  end
40
43
  let(:serialized_folder) { nil }
44
+ let(:server) { SERVER }
41
45
  let(:new_uid_validity) { nil }
42
- let(:local_uid) { "local_uid" }
43
46
 
44
47
  before do
45
48
  allow(Net::IMAP).to receive(:new) { imap }
46
49
  allow(imap).to receive(:list).with("", "") { [root_info] }
47
- allow(imap).to receive(:list).with(root_name, "*") { imap_folders }
50
+ allow(imap).to receive(:list).with(ROOT_NAME, "*") { imap_folders }
48
51
  allow(Imap::Backup::Utils).to receive(:make_folder)
49
52
  end
50
53
 
@@ -56,12 +59,13 @@ describe Imap::Backup::Account::Connection do
56
59
 
57
60
  describe "#initialize" do
58
61
  [
59
- [:username, "username@gmail.com"],
60
- [:local_path, "local_path"],
61
- [:backup_folders, [folder_config]]
62
+ [:username, USERNAME],
63
+ [:password, PASSWORD],
64
+ [:local_path, LOCAL_PATH],
65
+ [:server, SERVER]
62
66
  ].each do |attr, expected|
63
67
  it "expects #{attr}" do
64
- expect(subject.send(attr)).to eq(expected)
68
+ expect(subject.public_send(attr)).to eq(expected)
65
69
  end
66
70
  end
67
71
 
@@ -73,22 +77,106 @@ describe Imap::Backup::Account::Connection do
73
77
  end
74
78
 
75
79
  describe "#imap" do
76
- let!(:result) { subject.imap }
80
+ let(:result) { subject.imap }
77
81
 
78
82
  it "returns the IMAP connection" do
79
83
  expect(result).to eq(imap)
80
84
  end
81
85
 
82
- include_examples "connects to IMAP"
86
+ it "uses the password" do
87
+ result
88
+
89
+ expect(imap).to have_received(:login).with(USERNAME, PASSWORD)
90
+ end
91
+
92
+ context "with the GMail IMAP server" do
93
+ ACCESS_TOKEN = "access_token".freeze
94
+
95
+ let(:server) { GMAIL_IMAP_SERVER }
96
+ let(:refresh_token) { true }
97
+ let(:result) { nil }
98
+ let(:authenticator) do
99
+ instance_double(
100
+ Gmail::Authenticator,
101
+ credentials: credentials
102
+ )
103
+ end
104
+ let(:credentials) { OpenStruct.new(access_token: ACCESS_TOKEN) }
105
+
106
+ before do
107
+ allow(Gmail::Authenticator).
108
+ to receive(:refresh_token?) { refresh_token }
109
+ allow(Gmail::Authenticator).
110
+ to receive(:new).
111
+ with(email: USERNAME, token: PASSWORD) { authenticator }
112
+ end
113
+
114
+ context "when the password is our copy of a GMail refresh token" do
115
+ it "uses the OAuth2 access_token to authenticate" do
116
+ subject.imap
117
+
118
+ expect(imap).to have_received(:authenticate).with(
119
+ "XOAUTH2", USERNAME, ACCESS_TOKEN
120
+ )
121
+ end
122
+
123
+ context "when the refresh token is invalid" do
124
+ let(:credentials) { nil }
125
+
126
+ it "raises" do
127
+ expect { subject.imap }.to raise_error(String)
128
+ end
129
+ end
130
+ end
131
+
132
+ context "when the password is not our copy of a GMail refresh token" do
133
+ let(:refresh_token) { false }
134
+
135
+ it "uses the password" do
136
+ subject.imap
137
+
138
+ expect(imap).to have_received(:login).with(USERNAME, PASSWORD)
139
+ end
140
+ end
141
+ end
142
+
143
+ context "when the first login attempt fails" do
144
+ before do
145
+ outcomes = [-> { raise EOFError }, -> { true }]
146
+ allow(imap).to receive(:login) { outcomes.shift.call }
147
+ end
148
+
149
+ it "retries" do
150
+ subject.imap
151
+
152
+ expect(imap).to have_received(:login).twice
153
+ end
154
+ end
155
+
156
+ context "when run" do
157
+ before { subject.imap }
158
+
159
+ include_examples "connects to IMAP"
160
+ end
83
161
  end
84
162
 
85
163
  describe "#folders" do
86
164
  let(:imap_folders) do
87
- [instance_double(Net::IMAP::MailboxList)]
165
+ [instance_double(Net::IMAP::MailboxList, name: BACKUP_FOLDER)]
88
166
  end
89
167
 
90
168
  it "returns the list of folders" do
91
- expect(subject.folders).to eq(imap_folders)
169
+ expect(subject.folders).to eq([BACKUP_FOLDER])
170
+ end
171
+
172
+ context "with non-ASCII folder names" do
173
+ let(:imap_folders) do
174
+ [instance_double(Net::IMAP::MailboxList, name: "Gel&APY-scht")]
175
+ end
176
+
177
+ it "converts them to UTF-8" do
178
+ expect(subject.folders).to eq(["Gelöscht"])
179
+ end
92
180
  end
93
181
  end
94
182
 
@@ -104,11 +192,11 @@ describe Imap::Backup::Account::Connection do
104
192
  end
105
193
 
106
194
  it "returns the names of folders" do
107
- expect(subject.status[0][:name]).to eq(self.class.backup_folder)
195
+ expect(subject.status[0][:name]).to eq(BACKUP_FOLDER)
108
196
  end
109
197
 
110
198
  it "returns local message uids" do
111
- expect(subject.status[0][:local]).to eq([local_uid])
199
+ expect(subject.status[0][:local]).to eq([LOCAL_UID])
112
200
  end
113
201
 
114
202
  it "retrieves the available uids" do
@@ -132,16 +220,13 @@ describe Imap::Backup::Account::Connection do
132
220
  before do
133
221
  allow(Imap::Backup::Downloader).
134
222
  to receive(:new).with(folder, serializer) { downloader }
223
+ allow(Imap::Backup::Account::Folder).to receive(:new).
224
+ with(subject, BACKUP_FOLDER) { folder }
225
+ allow(Imap::Backup::Serializer::Mbox).to receive(:new).
226
+ with(LOCAL_PATH, BACKUP_FOLDER) { serializer }
135
227
  end
136
228
 
137
- context "with supplied backup_folders" do
138
- before do
139
- allow(Imap::Backup::Account::Folder).to receive(:new).
140
- with(subject, self.class.backup_folder) { folder }
141
- allow(Imap::Backup::Serializer::Mbox).to receive(:new).
142
- with(local_path, self.class.backup_folder) { serializer }
143
- end
144
-
229
+ context "with supplied config_folders" do
145
230
  it "runs the downloader" do
146
231
  expect(downloader).to receive(:run)
147
232
 
@@ -159,20 +244,20 @@ describe Imap::Backup::Account::Connection do
159
244
  end
160
245
  end
161
246
 
162
- context "without supplied backup_folders" do
247
+ context "without supplied config_folders" do
163
248
  let(:imap_folders) do
164
- [instance_double(Net::IMAP::MailboxList, name: "foo")]
249
+ [instance_double(Net::IMAP::MailboxList, name: ROOT_NAME)]
165
250
  end
166
251
 
167
252
  before do
168
253
  allow(Imap::Backup::Account::Folder).to receive(:new).
169
- with(subject, "foo") { folder }
254
+ with(subject, ROOT_NAME) { folder }
170
255
  allow(Imap::Backup::Serializer::Mbox).to receive(:new).
171
- with(local_path, "foo") { serializer }
256
+ with(LOCAL_PATH, ROOT_NAME) { serializer }
172
257
  end
173
258
 
174
- context "when supplied backup_folders is nil" do
175
- let(:backup_folders) { nil }
259
+ context "when supplied config_folders is nil" do
260
+ let(:config_folders) { nil }
176
261
 
177
262
  it "runs the downloader for each folder" do
178
263
  expect(downloader).to receive(:run).exactly(:once)
@@ -181,8 +266,8 @@ describe Imap::Backup::Account::Connection do
181
266
  end
182
267
  end
183
268
 
184
- context "when supplied backup_folders is an empty list" do
185
- let(:backup_folders) { [] }
269
+ context "when supplied config_folders is an empty list" do
270
+ let(:config_folders) { [] }
186
271
 
187
272
  it "runs the downloader for each folder" do
188
273
  expect(downloader).to receive(:run).exactly(:once)
@@ -192,14 +277,22 @@ describe Imap::Backup::Account::Connection do
192
277
  end
193
278
 
194
279
  context "when the imap server doesn't return folders" do
195
- let(:backup_folders) { nil }
280
+ let(:config_folders) { nil }
196
281
  let(:imap_folders) { nil }
197
282
 
198
- it "does not fail" do
199
- expect { subject.run_backup }.to_not raise_error
283
+ it "fails" do
284
+ expect do
285
+ subject.run_backup
286
+ end.to raise_error(RuntimeError, /Unable to get folder list/)
200
287
  end
201
288
  end
202
289
  end
290
+
291
+ context "when run" do
292
+ before { subject.run_backup }
293
+
294
+ include_examples "connects to IMAP"
295
+ end
203
296
  end
204
297
 
205
298
  describe "#restore" do
@@ -208,7 +301,7 @@ describe Imap::Backup::Account::Connection do
208
301
  Imap::Backup::Account::Folder,
209
302
  create: nil,
210
303
  uids: uids,
211
- name: "my_folder",
304
+ name: FOLDER_NAME,
212
305
  uid_validity: uid_validity
213
306
  )
214
307
  end
@@ -236,9 +329,9 @@ describe Imap::Backup::Account::Connection do
236
329
 
237
330
  before do
238
331
  allow(Imap::Backup::Account::Folder).to receive(:new).
239
- with(subject, "my_folder") { folder }
332
+ with(subject, FOLDER_NAME) { folder }
240
333
  allow(Imap::Backup::Serializer::Mbox).to receive(:new).
241
- with(anything, "my_folder") { serializer }
334
+ with(anything, FOLDER_NAME) { serializer }
242
335
  allow(Imap::Backup::Account::Folder).to receive(:new).
243
336
  with(subject, "new name") { updated_folder }
244
337
  allow(Imap::Backup::Serializer::Mbox).to receive(:new).
@@ -248,7 +341,7 @@ describe Imap::Backup::Account::Connection do
248
341
  allow(Imap::Backup::Uploader).to receive(:new).
249
342
  with(updated_folder, updated_serializer) { updated_uploader }
250
343
  allow(Pathname).to receive(:glob).
251
- and_yield(Pathname.new(File.join(local_path, "my_folder.imap")))
344
+ and_yield(Pathname.new(File.join(LOCAL_PATH, "#{FOLDER_NAME}.imap")))
252
345
  end
253
346
 
254
347
  it "sets local uid validity" do
@@ -1,7 +1,10 @@
1
1
  # rubocop:disable RSpec/PredicateMatcher
2
2
 
3
3
  describe Imap::Backup::Account::Folder do
4
- subject { described_class.new(connection, "my_folder") }
4
+ FOLDER_NAME = "Gelöscht".freeze
5
+ ENCODED_FOLDER_NAME = "Gel&APY-scht".freeze
6
+
7
+ subject { described_class.new(connection, FOLDER_NAME) }
5
8
 
6
9
  let(:imap) do
7
10
  instance_double(
@@ -16,7 +19,7 @@ describe Imap::Backup::Account::Folder do
16
19
  instance_double(Imap::Backup::Account::Connection, imap: imap)
17
20
  end
18
21
  let(:missing_mailbox_data) do
19
- OpenStruct.new(text: "Unknown Mailbox: my_folder")
22
+ OpenStruct.new(text: "Unknown Mailbox: #{FOLDER_NAME}")
20
23
  end
21
24
  let(:missing_mailbox_response) { OpenStruct.new(data: missing_mailbox_data) }
22
25
  let(:missing_mailbox_error) do
@@ -36,7 +39,8 @@ describe Imap::Backup::Account::Folder do
36
39
 
37
40
  context "with missing mailboxes" do
38
41
  before do
39
- allow(imap).to receive(:examine).and_raise(missing_mailbox_error)
42
+ allow(imap).to receive(:examine).
43
+ with(ENCODED_FOLDER_NAME).and_raise(missing_mailbox_error)
40
44
  end
41
45
 
42
46
  it "returns an empty array" do
@@ -50,7 +54,8 @@ describe Imap::Backup::Account::Folder do
50
54
  end
51
55
 
52
56
  before do
53
- allow(imap).to receive(:examine).and_raise(no_method_error)
57
+ allow(imap).to receive(:examine).
58
+ with(ENCODED_FOLDER_NAME).and_raise(missing_mailbox_error)
54
59
  end
55
60
 
56
61
  it "returns an empty array" do
@@ -82,7 +87,8 @@ describe Imap::Backup::Account::Folder do
82
87
 
83
88
  context "when the mailbox doesn't exist" do
84
89
  before do
85
- allow(imap).to receive(:examine).and_raise(missing_mailbox_error)
90
+ allow(imap).to receive(:examine).
91
+ with(ENCODED_FOLDER_NAME).and_raise(missing_mailbox_error)
86
92
  end
87
93
 
88
94
  it "is nil" do
@@ -97,11 +103,28 @@ describe Imap::Backup::Account::Folder do
97
103
  expect(subject.fetch(123)).to be_nil
98
104
  end
99
105
  end
106
+
107
+ context "when the first fetch_uid attempts fail" do
108
+ before do
109
+ outcomes = [-> { raise EOFError }, -> { [fetch_data_item] }]
110
+ allow(imap).to receive(:uid_fetch) { outcomes.shift.call }
111
+ end
112
+
113
+ it "retries" do
114
+ subject.fetch(123)
115
+
116
+ expect(imap).to have_received(:uid_fetch).twice
117
+ end
118
+
119
+ it "succeeds" do
120
+ subject.fetch(123)
121
+ end
122
+ end
100
123
  end
101
124
 
102
125
  describe "#folder" do
103
126
  it "is the name" do
104
- expect(subject.folder).to eq("my_folder")
127
+ expect(subject.folder).to eq(FOLDER_NAME)
105
128
  end
106
129
  end
107
130
 
@@ -114,7 +137,8 @@ describe Imap::Backup::Account::Folder do
114
137
 
115
138
  context "when the folder doesn't exist" do
116
139
  before do
117
- allow(imap).to receive(:examine).and_raise(missing_mailbox_error)
140
+ allow(imap).to receive(:examine).
141
+ with(ENCODED_FOLDER_NAME).and_raise(missing_mailbox_error)
118
142
  end
119
143
 
120
144
  it "is false" do
@@ -134,14 +158,21 @@ describe Imap::Backup::Account::Folder do
134
158
 
135
159
  context "when the folder doesn't exist" do
136
160
  before do
137
- allow(imap).to receive(:examine).and_raise(missing_mailbox_error)
161
+ allow(imap).to receive(:examine).
162
+ with(ENCODED_FOLDER_NAME).and_raise(missing_mailbox_error)
138
163
  end
139
164
 
140
- it "is does not create the folder" do
165
+ it "creates the folder" do
141
166
  expect(imap).to receive(:create)
142
167
 
143
168
  subject.create
144
169
  end
170
+
171
+ it "encodes the folder name" do
172
+ expect(imap).to receive(:create).with(ENCODED_FOLDER_NAME)
173
+
174
+ subject.create
175
+ end
145
176
  end
146
177
  end
147
178
 
@@ -154,7 +185,8 @@ describe Imap::Backup::Account::Folder do
154
185
 
155
186
  context "when the folder doesn't exist" do
156
187
  before do
157
- allow(imap).to receive(:examine).and_raise(missing_mailbox_error)
188
+ allow(imap).to receive(:examine).
189
+ with(ENCODED_FOLDER_NAME).and_raise(missing_mailbox_error)
158
190
  end
159
191
 
160
192
  it "raises an error" do
@@ -1,13 +1,16 @@
1
- # rubocop:disable RSpec/NestedGroups
2
-
3
1
  describe Imap::Backup::Configuration::Account do
4
- describe "#initialize" do
5
- subject { described_class.new(store, account, highline) }
2
+ ACCOUNT = "account".freeze
3
+ GMAIL_IMAP_SERVER = "imap.gmail.com".freeze
4
+ HIGHLINE = "highline".freeze
5
+ STORE = "store".freeze
6
+
7
+ subject { described_class.new(store, account, highline) }
6
8
 
7
- let(:store) { "store" }
8
- let(:account) { "account" }
9
- let(:highline) { "highline" }
9
+ let(:account) { ACCOUNT }
10
+ let(:highline) { HIGHLINE }
11
+ let(:store) { STORE }
10
12
 
13
+ describe "#initialize" do
11
14
  [:store, :account, :highline].each do |param|
12
15
  it "expects #{param}" do
13
16
  expect(subject.send(param)).to eq(send(param))
@@ -16,8 +19,6 @@ describe Imap::Backup::Configuration::Account do
16
19
  end
17
20
 
18
21
  describe "#run" do
19
- subject { described_class.new(store, account, highline) }
20
-
21
22
  let(:highline_menu_class) do
22
23
  Class.new do
23
24
  attr_reader :choices
@@ -46,7 +47,7 @@ describe Imap::Backup::Configuration::Account do
46
47
  let(:account) do
47
48
  {
48
49
  username: existing_email,
49
- server: existing_server,
50
+ server: current_server,
50
51
  local_path: "/backup/path",
51
52
  folders: [{name: "my_folder"}],
52
53
  password: existing_password
@@ -60,7 +61,7 @@ describe Imap::Backup::Configuration::Account do
60
61
  end
61
62
  let(:existing_email) { "user@example.com" }
62
63
  let(:new_email) { "foo@example.com" }
63
- let(:existing_server) { "imap.example.com" }
64
+ let(:current_server) { "imap.example.com" }
64
65
  let(:existing_password) { "password" }
65
66
  let(:other_email) { "other@example.com" }
66
67
  let(:other_existing_path) { "/other/existing/path" }
@@ -136,7 +137,7 @@ describe Imap::Backup::Configuration::Account do
136
137
  end
137
138
  end
138
139
 
139
- describe "email" do
140
+ describe "choosing 'modify email'" do
140
141
  before do
141
142
  allow(Imap::Backup::Configuration::Asker).
142
143
  to receive(:email) { new_email }
@@ -146,7 +147,7 @@ describe Imap::Backup::Configuration::Account do
146
147
 
147
148
  context "when the server is blank" do
148
149
  [
149
- ["GMail", "foo@gmail.com", "imap.gmail.com"],
150
+ ["GMail", "foo@gmail.com", GMAIL_IMAP_SERVER],
150
151
  ["Fastmail", "bar@fastmail.fm", "imap.fastmail.com"],
151
152
  ["Fastmail", "bar@fastmail.com", "imap.fastmail.com"]
152
153
  ].each do |service, email, expected|
@@ -154,7 +155,7 @@ describe Imap::Backup::Configuration::Account do
154
155
  let(:new_email) { email }
155
156
 
156
157
  context "with nil" do
157
- let(:existing_server) { nil }
158
+ let(:current_server) { nil }
158
159
 
159
160
  it "sets a default server" do
160
161
  expect(account[:server]).to eq(expected)
@@ -162,7 +163,7 @@ describe Imap::Backup::Configuration::Account do
162
163
  end
163
164
 
164
165
  context "with an empty string" do
165
- let(:existing_server) { "" }
166
+ let(:current_server) { "" }
166
167
 
167
168
  it "sets a default server" do
168
169
  expect(account[:server]).to eq(expected)
@@ -172,7 +173,7 @@ describe Imap::Backup::Configuration::Account do
172
173
  end
173
174
 
174
175
  context "when the domain is unrecognized" do
175
- let(:existing_server) { nil }
176
+ let(:current_server) { nil }
176
177
  let(:provider) do
177
178
  instance_double(Email::Provider, provider: :default)
178
179
  end
@@ -211,12 +212,18 @@ describe Imap::Backup::Configuration::Account do
211
212
  end
212
213
  end
213
214
 
214
- describe "password" do
215
+ describe "choosing 'modify password'" do
215
216
  let(:new_password) { "new_password" }
217
+ let(:gmail_oauth2) do
218
+ instance_double(Imap::Backup::Configuration::GmailOauth2, run: nil)
219
+ end
216
220
 
217
221
  before do
218
222
  allow(Imap::Backup::Configuration::Asker).
219
223
  to receive(:password) { new_password }
224
+ allow(Imap::Backup::Configuration::GmailOauth2).
225
+ to receive(:new).
226
+ with(account) { gmail_oauth2 }
220
227
  subject.run
221
228
  menu.choices["modify password"].call
222
229
  end
@@ -238,9 +245,17 @@ describe Imap::Backup::Configuration::Account do
238
245
 
239
246
  include_examples "it doesn't flag the account as modified"
240
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
241
256
  end
242
257
 
243
- describe "server" do
258
+ describe "choosing 'modify server'" do
244
259
  let(:server) { "server" }
245
260
 
246
261
  before do
@@ -258,7 +273,7 @@ describe Imap::Backup::Configuration::Account do
258
273
  include_examples "it flags the account as modified"
259
274
  end
260
275
 
261
- describe "backup_path" do
276
+ describe "choosing 'modify backup path'" do
262
277
  let(:new_backup_path) { "/new/path" }
263
278
 
264
279
  before do
@@ -296,7 +311,7 @@ describe Imap::Backup::Configuration::Account do
296
311
  include_examples "it flags the account as modified"
297
312
  end
298
313
 
299
- describe "folders" do
314
+ describe "choosing 'choose backup folders'" do
300
315
  let(:chooser) do
301
316
  instance_double(Imap::Backup::Configuration::FolderChooser, run: nil)
302
317
  end
@@ -313,7 +328,7 @@ describe Imap::Backup::Configuration::Account do
313
328
  end
314
329
  end
315
330
 
316
- describe "connection test" do
331
+ describe "choosing 'test connection'" do
317
332
  before do
318
333
  allow(Imap::Backup::Configuration::ConnectionTester).
319
334
  to receive(:test) { "All fine" }
@@ -328,7 +343,7 @@ describe Imap::Backup::Configuration::Account do
328
343
  end
329
344
  end
330
345
 
331
- describe "deletion" do
346
+ describe "choosing 'delete'" do
332
347
  let(:confirmed) { true }
333
348
 
334
349
  before do
@@ -355,5 +370,3 @@ describe Imap::Backup::Configuration::Account do
355
370
  end
356
371
  end
357
372
  end
358
-
359
- # rubocop:enable RSpec/NestedGroups