imap-backup 2.2.2 → 3.0.0.rc1

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