imap-backup 2.2.2 → 3.2.1

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 (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