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.
- checksums.yaml +4 -4
- data/.rubocop.yml +2 -2
- data/.rubocop_todo.yml +10 -26
- data/README.md +4 -11
- data/bin/imap-backup +3 -0
- data/docs/01-credentials-screen.png +0 -0
- data/docs/02-new-project.png +0 -0
- data/docs/03-initial-credentials-for-project.png +0 -0
- data/docs/04-credential-type-selection.png +0 -0
- data/docs/05-cant-create-without-consent-setup.png +0 -0
- data/docs/06-user-type-selection.png +0 -0
- data/docs/07-consent-screen-form.png +0 -0
- data/docs/08-app-scopes.png +0 -0
- data/docs/09-scope-selection.png +0 -0
- data/docs/10-updated-app-scopes.png +0 -0
- data/docs/11-test-users.png +0 -0
- data/docs/12-add-users.png +0 -0
- data/docs/13-create-oauth-client.png +0 -0
- data/docs/14-application-details.png +0 -0
- data/docs/16-initial-menu.png +0 -0
- data/docs/17-inputting-the-email-address.png +0 -0
- data/docs/18-choose-password.png +0 -0
- data/docs/19-supply-client-info.png +0 -0
- data/docs/20-choose-gmail-account.png +0 -0
- data/docs/21-accept-warnings.png +0 -0
- data/docs/22-grant-access.png +0 -0
- data/docs/24-confirm-choices.png +0 -0
- data/docs/25-success-code.png +0 -0
- data/docs/26-type-code-into-imap-backup.png +0 -0
- data/docs/27-success.png +0 -0
- data/docs/setting-up-gmail.md +166 -0
- data/imap-backup.gemspec +2 -8
- data/lib/email/mboxrd/message.rb +2 -0
- data/lib/email/provider.rb +3 -1
- data/lib/gmail/authenticator.rb +160 -0
- data/lib/google/auth/stores/in_memory_token_store.rb +9 -0
- data/lib/imap/backup.rb +2 -1
- data/lib/imap/backup/account/connection.rb +31 -13
- data/lib/imap/backup/configuration/account.rb +9 -1
- data/lib/imap/backup/configuration/gmail_oauth2.rb +82 -0
- data/lib/imap/backup/configuration/setup.rb +4 -1
- data/lib/imap/backup/version.rb +5 -4
- data/spec/features/backup_spec.rb +1 -1
- data/spec/unit/gmail/authenticator_spec.rb +138 -0
- data/spec/unit/google/auth/stores/in_memory_token_store_spec.rb +15 -0
- data/spec/unit/imap/backup/account/connection_spec.rb +103 -40
- data/spec/unit/imap/backup/configuration/account_spec.rb +37 -24
- data/spec/unit/imap/backup/configuration/gmail_oauth2_spec.rb +84 -0
- data/spec/unit/imap/backup/configuration/setup_spec.rb +51 -31
- metadata +68 -9
data/lib/imap/backup.rb
CHANGED
@@ -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(
|
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
|
-
|
82
|
-
|
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
|
137
|
-
|
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 =
|
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)
|
data/lib/imap/backup/version.rb
CHANGED
@@ -1,8 +1,9 @@
|
|
1
1
|
module Imap; end
|
2
2
|
|
3
3
|
module Imap::Backup
|
4
|
-
MAJOR =
|
5
|
-
MINOR =
|
6
|
-
REVISION =
|
7
|
-
|
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
|
@@ -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
|
-
|
2
|
-
def self.backup_folder
|
3
|
-
"backup_folder"
|
4
|
-
end
|
1
|
+
require "ostruct"
|
5
2
|
|
6
|
-
|
7
|
-
|
8
|
-
|
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:
|
19
|
-
password:
|
20
|
-
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(:
|
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:
|
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: [
|
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(
|
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,
|
60
|
-
[:
|
61
|
-
[:
|
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.
|
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(
|
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([
|
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:
|
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,
|
226
|
+
with(subject, ROOT_NAME) { folder }
|
170
227
|
allow(Imap::Backup::Serializer::Mbox).to receive(:new).
|
171
|
-
with(
|
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:
|
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,
|
302
|
+
with(subject, FOLDER_NAME) { folder }
|
240
303
|
allow(Imap::Backup::Serializer::Mbox).to receive(:new).
|
241
|
-
with(anything,
|
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(
|
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
|