imap-backup 2.2.2 → 3.0.0.rc1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (50) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +2 -2
  3. data/.rubocop_todo.yml +10 -26
  4. data/README.md +4 -11
  5. data/bin/imap-backup +3 -0
  6. data/docs/01-credentials-screen.png +0 -0
  7. data/docs/02-new-project.png +0 -0
  8. data/docs/03-initial-credentials-for-project.png +0 -0
  9. data/docs/04-credential-type-selection.png +0 -0
  10. data/docs/05-cant-create-without-consent-setup.png +0 -0
  11. data/docs/06-user-type-selection.png +0 -0
  12. data/docs/07-consent-screen-form.png +0 -0
  13. data/docs/08-app-scopes.png +0 -0
  14. data/docs/09-scope-selection.png +0 -0
  15. data/docs/10-updated-app-scopes.png +0 -0
  16. data/docs/11-test-users.png +0 -0
  17. data/docs/12-add-users.png +0 -0
  18. data/docs/13-create-oauth-client.png +0 -0
  19. data/docs/14-application-details.png +0 -0
  20. data/docs/16-initial-menu.png +0 -0
  21. data/docs/17-inputting-the-email-address.png +0 -0
  22. data/docs/18-choose-password.png +0 -0
  23. data/docs/19-supply-client-info.png +0 -0
  24. data/docs/20-choose-gmail-account.png +0 -0
  25. data/docs/21-accept-warnings.png +0 -0
  26. data/docs/22-grant-access.png +0 -0
  27. data/docs/24-confirm-choices.png +0 -0
  28. data/docs/25-success-code.png +0 -0
  29. data/docs/26-type-code-into-imap-backup.png +0 -0
  30. data/docs/27-success.png +0 -0
  31. data/docs/setting-up-gmail.md +166 -0
  32. data/imap-backup.gemspec +2 -8
  33. data/lib/email/mboxrd/message.rb +2 -0
  34. data/lib/email/provider.rb +3 -1
  35. data/lib/gmail/authenticator.rb +160 -0
  36. data/lib/google/auth/stores/in_memory_token_store.rb +9 -0
  37. data/lib/imap/backup.rb +2 -1
  38. data/lib/imap/backup/account/connection.rb +31 -13
  39. data/lib/imap/backup/configuration/account.rb +9 -1
  40. data/lib/imap/backup/configuration/gmail_oauth2.rb +82 -0
  41. data/lib/imap/backup/configuration/setup.rb +4 -1
  42. data/lib/imap/backup/version.rb +5 -4
  43. data/spec/features/backup_spec.rb +1 -1
  44. data/spec/unit/gmail/authenticator_spec.rb +138 -0
  45. data/spec/unit/google/auth/stores/in_memory_token_store_spec.rb +15 -0
  46. data/spec/unit/imap/backup/account/connection_spec.rb +103 -40
  47. data/spec/unit/imap/backup/configuration/account_spec.rb +37 -24
  48. data/spec/unit/imap/backup/configuration/gmail_oauth2_spec.rb +84 -0
  49. data/spec/unit/imap/backup/configuration/setup_spec.rb +51 -31
  50. metadata +68 -9
@@ -0,0 +1,9 @@
1
+ module Google; end
2
+ module Google::Auth; end
3
+ module Google::Auth::Stores; end
4
+
5
+ class Google::Auth::Stores::InMemoryTokenStore < Hash
6
+ def load(id)
7
+ self[id]
8
+ end
9
+ end
@@ -7,6 +7,7 @@ require "imap/backup/configuration/account"
7
7
  require "imap/backup/configuration/asker"
8
8
  require "imap/backup/configuration/connection_tester"
9
9
  require "imap/backup/configuration/folder_chooser"
10
+ require "imap/backup/configuration/gmail_oauth2"
10
11
  require "imap/backup/configuration/list"
11
12
  require "imap/backup/configuration/setup"
12
13
  require "imap/backup/configuration/store"
@@ -28,7 +29,7 @@ module Imap::Backup
28
29
  attr_reader :logger
29
30
 
30
31
  def initialize
31
- @logger = ::Logger.new(STDOUT)
32
+ @logger = ::Logger.new($stdout)
32
33
  end
33
34
  end
34
35
 
@@ -1,9 +1,14 @@
1
1
  require "net/imap"
2
+ require "gmail_xoauth"
3
+
4
+ require "gmail/authenticator"
2
5
 
3
6
  module Imap::Backup
4
7
  module Account; end
5
8
 
6
9
  class Account::Connection
10
+ class InvalidGmailOauth2RefreshToken < StandardError; end
11
+
7
12
  attr_reader :connection_options
8
13
  attr_reader :local_path
9
14
  attr_reader :password
@@ -78,12 +83,34 @@ module Imap::Backup
78
83
  "Creating IMAP instance: #{server}, options: #{options.inspect}"
79
84
  )
80
85
  @imap = Net::IMAP.new(server, options)
81
- Imap::Backup.logger.debug "Logging in: #{username}/#{masked_password}"
82
- @imap.login(username, password)
86
+ if gmail? && Gmail::Authenticator.refresh_token?(password)
87
+ authenticator = Gmail::Authenticator.new(email: username, token: password)
88
+ credentials = authenticator.credentials
89
+ raise InvalidGmailOauth2RefreshToken if !credentials
90
+
91
+ Imap::Backup.logger.debug "Logging in with OAuth2 token: #{username}"
92
+ @imap.authenticate("XOAUTH2", username, credentials.access_token)
93
+ else
94
+ Imap::Backup.logger.debug "Logging in: #{username}/#{masked_password}"
95
+ @imap.login(username, password)
96
+ end
83
97
  Imap::Backup.logger.debug "Login complete"
84
98
  @imap
85
99
  end
86
100
 
101
+ def server
102
+ return @server if @server
103
+ return nil if provider.nil?
104
+
105
+ @server = provider.host
106
+ end
107
+
108
+ def backup_folders
109
+ return @backup_folders if @backup_folders && !@backup_folders.empty?
110
+
111
+ (folders || []).map { |f| {name: f.name} }
112
+ end
113
+
87
114
  private
88
115
 
89
116
  def each_folder
@@ -133,10 +160,8 @@ module Imap::Backup
133
160
  password.gsub(/./, "x")
134
161
  end
135
162
 
136
- def backup_folders
137
- return @backup_folders if @backup_folders && !@backup_folders.empty?
138
-
139
- (folders || []).map { |f| {name: f.name} }
163
+ def gmail?
164
+ server == Email::Provider::GMAIL_IMAP_SERVER
140
165
  end
141
166
 
142
167
  def local_folders
@@ -154,13 +179,6 @@ module Imap::Backup
154
179
  @provider ||= Email::Provider.for_address(username)
155
180
  end
156
181
 
157
- def server
158
- return @server if @server
159
- return nil if provider.nil?
160
-
161
- @server = provider.host
162
- end
163
-
164
182
  def provider_options
165
183
  provider.options.merge(connection_options)
166
184
  end
@@ -56,9 +56,11 @@ module Imap::Backup
56
56
  )
57
57
  else
58
58
  account[:username] = username
59
+ # rubocop:disable Style/IfUnlessModifier
59
60
  if account[:server].nil? || (account[:server] == "")
60
61
  account[:server] = default_server(username)
61
62
  end
63
+ # rubocop:enable Style/IfUnlessModifier
62
64
  account[:modified] = true
63
65
  end
64
66
  end
@@ -66,7 +68,13 @@ module Imap::Backup
66
68
 
67
69
  def modify_password(menu)
68
70
  menu.choice("modify password") do
69
- password = Configuration::Asker.password
71
+ password =
72
+ if account[:server] == Email::Provider::GMAIL_IMAP_SERVER
73
+ Configuration::GmailOauth2.new(account).run
74
+ else
75
+ Configuration::Asker.password
76
+ end
77
+
70
78
  if !password.nil?
71
79
  account[:password] = password
72
80
  account[:modified] = true
@@ -0,0 +1,82 @@
1
+ module Imap::Backup
2
+ module Configuration; end
3
+
4
+ class Configuration::GmailOauth2
5
+ BANNER = <<~BANNER.freeze
6
+ GMail OAuth2 Setup
7
+
8
+ You need to authorize imap_backup to get access to your email.
9
+ To do so, please follow the instructions here:
10
+
11
+ https://github.com/joeyates/imap-backup/docs/setting-up-gmail.md
12
+
13
+ BANNER
14
+
15
+ GMAIL_READ_SCOPE = "https://mail.google.com/".freeze
16
+ OOB_URI = "urn:ietf:wg:oauth:2.0:oob".freeze
17
+
18
+ attr_reader :account
19
+ attr_reader :client_id
20
+ attr_reader :client_secret
21
+
22
+ def initialize(account)
23
+ @account = account
24
+ end
25
+
26
+ def run
27
+ Kernel.system("clear")
28
+ Kernel.puts BANNER
29
+ @client_id = highline.ask("client_id: ")
30
+ @client_secret = highline.ask("client_secret: ")
31
+
32
+ Kernel.puts <<~MESSAGE
33
+
34
+ Open the following URL in your browser
35
+
36
+ #{authorization_url}
37
+
38
+ Then copy the success code
39
+
40
+ MESSAGE
41
+
42
+ @code = highline.ask("success code: ")
43
+ @credentials = authorizer.get_and_store_credentials_from_code(
44
+ user_id: email, code: @code, base_url: OOB_URI
45
+ )
46
+
47
+ raise "Failed" if !@credentials
48
+
49
+ token = JSON.parse(token_store.load(email))
50
+ token["client_secret"] = client_secret
51
+ token.to_json
52
+ end
53
+
54
+ private
55
+
56
+ def email
57
+ account[:username]
58
+ end
59
+
60
+ def highline
61
+ Configuration::Setup.highline
62
+ end
63
+
64
+ def auth_client_id
65
+ @auth_client_id = Google::Auth::ClientId.new(client_id, client_secret)
66
+ end
67
+
68
+ def authorizer
69
+ @authorizer ||= Google::Auth::UserAuthorizer.new(
70
+ auth_client_id, GMAIL_READ_SCOPE, token_store
71
+ )
72
+ end
73
+
74
+ def token_store
75
+ @token_store ||= Google::Auth::Stores::InMemoryTokenStore.new
76
+ end
77
+
78
+ def authorization_url
79
+ authorizer.get_authorization_url(base_url: OOB_URI)
80
+ end
81
+ end
82
+ end
@@ -75,7 +75,10 @@ module Imap::Backup
75
75
  password: "",
76
76
  local_path: File.join(config.path, username.tr("@", "_")),
77
77
  folders: []
78
- }
78
+ }.tap do |c|
79
+ server = Email::Provider.for_address(username)
80
+ c[:server] = server.host if server.host
81
+ end
79
82
  end
80
83
 
81
84
  def edit_account(username)
@@ -1,8 +1,9 @@
1
1
  module Imap; end
2
2
 
3
3
  module Imap::Backup
4
- MAJOR = 2
5
- MINOR = 2
6
- REVISION = 2
7
- VERSION = [MAJOR, MINOR, REVISION].compact.map(&:to_s).join(".")
4
+ MAJOR = 3
5
+ MINOR = 0
6
+ REVISION = 0
7
+ PRE = "rc1".freeze
8
+ VERSION = [MAJOR, MINOR, REVISION, PRE].compact.map(&:to_s).join(".")
8
9
  end
@@ -38,7 +38,7 @@ RSpec.describe "backup", type: :feature, docker: true do
38
38
  end
39
39
 
40
40
  it "saves a file version" do
41
- expect(imap_metadata[:version].to_s).to match(/^[0-9\.]$/)
41
+ expect(imap_metadata[:version].to_s).to match(/^[0-9.]$/)
42
42
  end
43
43
 
44
44
  it "records IMAP ids" do
@@ -0,0 +1,138 @@
1
+ require "gmail/authenticator"
2
+ require "googleauth"
3
+
4
+ describe Gmail::Authenticator do
5
+ ACCESS_TOKEN = "access_token".freeze
6
+ AUTHORIZATION_URL = "authorization_url".freeze
7
+ CLIENT_ID = "client_id".freeze
8
+ CLIENT_SECRET = "client_secret".freeze
9
+ CODE = "code".freeze
10
+ CREDENTIALS = "credentials".freeze
11
+ EMAIL = "email".freeze
12
+ EXPIRATION_TIME_MILLIS = "expiration_time_millis".freeze
13
+ GMAIL_READ_SCOPE = "https://mail.google.com/".freeze
14
+ IMAP_BACKUP_TOKEN = "imap_backup_token".freeze
15
+ OOB_URI = "urn:ietf:wg:oauth:2.0:oob".freeze
16
+ REFRESH_TOKEN = "refresh_token".freeze
17
+
18
+ subject { described_class.new(**params) }
19
+
20
+ let(:params) do
21
+ {
22
+ email: EMAIL,
23
+ token: IMAP_BACKUP_TOKEN
24
+ }
25
+ end
26
+
27
+ let(:authorizer) do
28
+ instance_double(Google::Auth::UserAuthorizer)
29
+ end
30
+
31
+ let(:imap_backup_token) do
32
+ instance_double(
33
+ Gmail::Authenticator::ImapBackupToken,
34
+ access_token: ACCESS_TOKEN,
35
+ client_id: CLIENT_ID,
36
+ client_secret: CLIENT_SECRET,
37
+ expiration_time_millis: EXPIRATION_TIME_MILLIS,
38
+ refresh_token: REFRESH_TOKEN,
39
+ valid?: true
40
+ )
41
+ end
42
+
43
+ let(:token_store) do
44
+ instance_double(Google::Auth::Stores::InMemoryTokenStore)
45
+ end
46
+
47
+ let(:credentials) do
48
+ instance_double(Google::Auth::UserRefreshCredentials, refresh!: true)
49
+ end
50
+
51
+ let(:expired) { false }
52
+
53
+ before do
54
+ allow(Google::Auth::UserAuthorizer).
55
+ to receive(:new).
56
+ with(
57
+ instance_of(Google::Auth::ClientId),
58
+ GMAIL_READ_SCOPE,
59
+ token_store
60
+ ) { authorizer }
61
+ allow(authorizer).to receive(:get_authorization_url).
62
+ with(base_url: OOB_URI) { AUTHORIZATION_URL }
63
+ allow(authorizer).to receive(:get_credentials).
64
+ with(EMAIL) { credentials }
65
+ allow(authorizer).to receive(:get_credentials_from_code).
66
+ with(user_id: EMAIL, code: CODE, base_url: OOB_URI) { CREDENTIALS }
67
+
68
+ allow(Google::Auth::UserRefreshCredentials).
69
+ to receive(:new) { credentials }
70
+ allow(credentials).to receive(:expired?) { expired }
71
+
72
+ allow(Google::Auth::Stores::InMemoryTokenStore).
73
+ to receive(:new) { token_store }
74
+ allow(token_store).to receive(:store).
75
+ with(EMAIL, anything) # TODO: use a JSON matcher
76
+ allow(Gmail::Authenticator::ImapBackupToken).
77
+ to receive(:new).
78
+ with(IMAP_BACKUP_TOKEN) { imap_backup_token }
79
+ end
80
+
81
+ describe "#initialize" do
82
+ [:email, :token].each do |param|
83
+ context "parameter #{param}" do
84
+ let(:params) { super().dup.reject { |k| k == param } }
85
+
86
+ it "is expected" do
87
+ expect { subject }.to raise_error(
88
+ ArgumentError, /missing keyword: :#{param}/
89
+ )
90
+ end
91
+ end
92
+ end
93
+ end
94
+
95
+ describe "#credentials" do
96
+ let!(:result) { subject.credentials }
97
+
98
+ it "attempts to get credentials" do
99
+ expect(authorizer).to have_received(:get_credentials)
100
+ end
101
+
102
+ it "returns the result" do
103
+ expect(result).to eq(credentials)
104
+ end
105
+
106
+ context "when the access_token has expired" do
107
+ let(:expired) { true }
108
+
109
+ it "refreshes it" do
110
+ expect(credentials).to have_received(:refresh!)
111
+ end
112
+ end
113
+ end
114
+
115
+ describe "#authorization_url" do
116
+ let!(:result) { subject.authorization_url }
117
+
118
+ it "requests an authorization URL" do
119
+ expect(authorizer).to have_received(:get_authorization_url)
120
+ end
121
+
122
+ it "returns the result" do
123
+ expect(result).to eq(AUTHORIZATION_URL)
124
+ end
125
+ end
126
+
127
+ describe "#credentials_from_code" do
128
+ let!(:result) { subject.credentials_from_code(CODE) }
129
+
130
+ it "requests credentials" do
131
+ expect(authorizer).to have_received(:get_credentials_from_code)
132
+ end
133
+
134
+ it "returns credentials" do
135
+ expect(result).to eq(CREDENTIALS)
136
+ end
137
+ end
138
+ end
@@ -0,0 +1,15 @@
1
+ require "google/auth/stores/in_memory_token_store"
2
+
3
+ describe Google::Auth::Stores::InMemoryTokenStore do
4
+ KEY = "key".freeze
5
+ VALUE = "value".freeze
6
+
7
+ subject { described_class.new }
8
+
9
+ describe "#load" do
10
+ it "returns an item's value" do
11
+ subject[KEY] = VALUE
12
+ expect(subject.load(KEY)).to eq(VALUE)
13
+ end
14
+ end
15
+ end
@@ -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: backup_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(:backup_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,14 @@ 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
+ [:backup_folders, [FOLDER_CONFIG]],
66
+ [:server, SERVER]
62
67
  ].each do |attr, expected|
63
68
  it "expects #{attr}" do
64
- expect(subject.send(attr)).to eq(expected)
69
+ expect(subject.public_send(attr)).to eq(expected)
65
70
  end
66
71
  end
67
72
 
@@ -79,6 +84,61 @@ describe Imap::Backup::Account::Connection do
79
84
  expect(result).to eq(imap)
80
85
  end
81
86
 
87
+ it "uses the password" do
88
+ expect(imap).to have_received(:login).with(USERNAME, PASSWORD)
89
+ end
90
+
91
+ context "with the GMail IMAP server" do
92
+ ACCESS_TOKEN = "access_token".freeze
93
+
94
+ let(:server) { GMAIL_IMAP_SERVER }
95
+ let(:refresh_token) { true }
96
+ let(:result) { nil }
97
+ let(:authenticator) do
98
+ instance_double(
99
+ Gmail::Authenticator,
100
+ credentials: credentials
101
+ )
102
+ end
103
+ let(:credentials) { OpenStruct.new(access_token: ACCESS_TOKEN) }
104
+
105
+ before do
106
+ allow(Gmail::Authenticator).
107
+ to receive(:refresh_token?) { refresh_token }
108
+ allow(Gmail::Authenticator).
109
+ to receive(:new).
110
+ with(email: USERNAME, token: PASSWORD) { authenticator }
111
+ end
112
+
113
+ context "when the password is our copy of a GMail refresh token" do
114
+ it "uses the OAuth2 access_token to authenticate" do
115
+ subject.imap
116
+
117
+ expect(imap).to have_received(:authenticate).with(
118
+ "XOAUTH2", USERNAME, ACCESS_TOKEN
119
+ )
120
+ end
121
+
122
+ context "when the refresh token is invalid" do
123
+ let(:credentials) { nil }
124
+
125
+ it "raises" do
126
+ expect { subject.imap }.to raise_error(String)
127
+ end
128
+ end
129
+ end
130
+
131
+ context "when the password is not our copy of a GMail refresh token" do
132
+ let(:refresh_token) { false }
133
+
134
+ it "uses the password" do
135
+ subject.imap
136
+
137
+ expect(imap).to have_received(:login).with(USERNAME, PASSWORD)
138
+ end
139
+ end
140
+ end
141
+
82
142
  include_examples "connects to IMAP"
83
143
  end
84
144
 
@@ -104,11 +164,11 @@ describe Imap::Backup::Account::Connection do
104
164
  end
105
165
 
106
166
  it "returns the names of folders" do
107
- expect(subject.status[0][:name]).to eq(self.class.backup_folder)
167
+ expect(subject.status[0][:name]).to eq(BACKUP_FOLDER)
108
168
  end
109
169
 
110
170
  it "returns local message uids" do
111
- expect(subject.status[0][:local]).to eq([local_uid])
171
+ expect(subject.status[0][:local]).to eq([LOCAL_UID])
112
172
  end
113
173
 
114
174
  it "retrieves the available uids" do
@@ -132,16 +192,13 @@ describe Imap::Backup::Account::Connection do
132
192
  before do
133
193
  allow(Imap::Backup::Downloader).
134
194
  to receive(:new).with(folder, serializer) { downloader }
195
+ allow(Imap::Backup::Account::Folder).to receive(:new).
196
+ with(subject, BACKUP_FOLDER) { folder }
197
+ allow(Imap::Backup::Serializer::Mbox).to receive(:new).
198
+ with(LOCAL_PATH, BACKUP_FOLDER) { serializer }
135
199
  end
136
200
 
137
201
  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
-
145
202
  it "runs the downloader" do
146
203
  expect(downloader).to receive(:run)
147
204
 
@@ -161,14 +218,14 @@ describe Imap::Backup::Account::Connection do
161
218
 
162
219
  context "without supplied backup_folders" do
163
220
  let(:imap_folders) do
164
- [instance_double(Net::IMAP::MailboxList, name: "foo")]
221
+ [instance_double(Net::IMAP::MailboxList, name: ROOT_NAME)]
165
222
  end
166
223
 
167
224
  before do
168
225
  allow(Imap::Backup::Account::Folder).to receive(:new).
169
- with(subject, "foo") { folder }
226
+ with(subject, ROOT_NAME) { folder }
170
227
  allow(Imap::Backup::Serializer::Mbox).to receive(:new).
171
- with(local_path, "foo") { serializer }
228
+ with(LOCAL_PATH, ROOT_NAME) { serializer }
172
229
  end
173
230
 
174
231
  context "when supplied backup_folders is nil" do
@@ -200,6 +257,12 @@ describe Imap::Backup::Account::Connection do
200
257
  end
201
258
  end
202
259
  end
260
+
261
+ context "when run" do
262
+ before { subject.run_backup }
263
+
264
+ include_examples "connects to IMAP"
265
+ end
203
266
  end
204
267
 
205
268
  describe "#restore" do
@@ -208,7 +271,7 @@ describe Imap::Backup::Account::Connection do
208
271
  Imap::Backup::Account::Folder,
209
272
  create: nil,
210
273
  uids: uids,
211
- name: "my_folder",
274
+ name: FOLDER_NAME,
212
275
  uid_validity: uid_validity
213
276
  )
214
277
  end
@@ -236,9 +299,9 @@ describe Imap::Backup::Account::Connection do
236
299
 
237
300
  before do
238
301
  allow(Imap::Backup::Account::Folder).to receive(:new).
239
- with(subject, "my_folder") { folder }
302
+ with(subject, FOLDER_NAME) { folder }
240
303
  allow(Imap::Backup::Serializer::Mbox).to receive(:new).
241
- with(anything, "my_folder") { serializer }
304
+ with(anything, FOLDER_NAME) { serializer }
242
305
  allow(Imap::Backup::Account::Folder).to receive(:new).
243
306
  with(subject, "new name") { updated_folder }
244
307
  allow(Imap::Backup::Serializer::Mbox).to receive(:new).
@@ -248,7 +311,7 @@ describe Imap::Backup::Account::Connection do
248
311
  allow(Imap::Backup::Uploader).to receive(:new).
249
312
  with(updated_folder, updated_serializer) { updated_uploader }
250
313
  allow(Pathname).to receive(:glob).
251
- and_yield(Pathname.new(File.join(local_path, "my_folder.imap")))
314
+ and_yield(Pathname.new(File.join(LOCAL_PATH, "#{FOLDER_NAME}.imap")))
252
315
  end
253
316
 
254
317
  it "sets local uid validity" do