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