imap-backup 2.1.1 → 3.0.0
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 +5 -4
- data/.rubocop_todo.yml +29 -11
- data/.travis.yml +1 -1
- data/README.md +10 -13
- data/bin/imap-backup +5 -2
- 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 +3 -9
- data/lib/email/mboxrd/message.rb +4 -3
- 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 +59 -34
- data/lib/imap/backup/account/folder.rb +10 -1
- 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/serializer/mbox.rb +4 -0
- data/lib/imap/backup/serializer/mbox_enumerator.rb +1 -1
- data/lib/imap/backup/serializer/mbox_store.rb +20 -4
- data/lib/imap/backup/uploader.rb +10 -2
- data/lib/imap/backup/version.rb +5 -4
- data/spec/features/backup_spec.rb +3 -3
- data/spec/features/helper.rb +1 -1
- data/spec/features/restore_spec.rb +75 -27
- data/spec/features/support/backup_directory.rb +2 -2
- data/spec/features/support/email_server.rb +1 -3
- data/spec/features/support/shared/message_fixtures.rb +8 -0
- data/spec/spec_helper.rb +1 -1
- data/spec/support/fixtures.rb +1 -1
- data/spec/unit/email/mboxrd/message_spec.rb +2 -8
- data/spec/unit/email/provider_spec.rb +2 -2
- 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 +157 -79
- data/spec/unit/imap/backup/account/folder_spec.rb +30 -20
- data/spec/unit/imap/backup/configuration/account_spec.rb +65 -46
- data/spec/unit/imap/backup/configuration/asker_spec.rb +20 -17
- data/spec/unit/imap/backup/configuration/connection_tester_spec.rb +6 -10
- data/spec/unit/imap/backup/configuration/folder_chooser_spec.rb +16 -10
- data/spec/unit/imap/backup/configuration/gmail_oauth2_spec.rb +84 -0
- data/spec/unit/imap/backup/configuration/list_spec.rb +6 -3
- data/spec/unit/imap/backup/configuration/setup_spec.rb +89 -54
- data/spec/unit/imap/backup/configuration/store_spec.rb +18 -16
- data/spec/unit/imap/backup/downloader_spec.rb +14 -14
- data/spec/unit/imap/backup/serializer/mbox_enumerator_spec.rb +6 -1
- data/spec/unit/imap/backup/serializer/mbox_spec.rb +62 -40
- data/spec/unit/imap/backup/serializer/mbox_store_spec.rb +94 -35
- data/spec/unit/imap/backup/uploader_spec.rb +23 -7
- data/spec/unit/imap/backup/utils_spec.rb +10 -9
- metadata +68 -9
@@ -0,0 +1,84 @@
|
|
1
|
+
describe Imap::Backup::Configuration::GmailOauth2 do
|
2
|
+
include HighLineTestHelpers
|
3
|
+
|
4
|
+
subject { described_class.new(account) }
|
5
|
+
|
6
|
+
let(:authorization_url) { "some long authorization_url" }
|
7
|
+
let(:credentials) { "credentials" }
|
8
|
+
let(:json_token) { '{"sentinel":"foo"}' }
|
9
|
+
let!(:highline_streams) { prepare_highline }
|
10
|
+
let(:highline) { Imap::Backup::Configuration::Setup.highline }
|
11
|
+
let(:input) { highline_streams[0] }
|
12
|
+
let(:output) { highline_streams[1] }
|
13
|
+
let(:account) { {} }
|
14
|
+
|
15
|
+
let(:authorizer) do
|
16
|
+
instance_double(
|
17
|
+
Google::Auth::UserAuthorizer,
|
18
|
+
get_authorization_url: authorization_url,
|
19
|
+
get_and_store_credentials_from_code: credentials
|
20
|
+
)
|
21
|
+
end
|
22
|
+
let(:token_store) do
|
23
|
+
instance_double(
|
24
|
+
Google::Auth::Stores::InMemoryTokenStore,
|
25
|
+
load: json_token
|
26
|
+
)
|
27
|
+
end
|
28
|
+
|
29
|
+
before do
|
30
|
+
allow(Google::Auth::UserAuthorizer).
|
31
|
+
to receive(:new) { authorizer }
|
32
|
+
allow(Google::Auth::Stores::InMemoryTokenStore).
|
33
|
+
to receive(:new) { token_store }
|
34
|
+
|
35
|
+
allow(highline).to receive(:ask).and_call_original
|
36
|
+
|
37
|
+
allow(Kernel).to receive(:system)
|
38
|
+
allow(Kernel).to receive(:puts)
|
39
|
+
|
40
|
+
allow(input).to receive(:gets).and_return(
|
41
|
+
"my_client_id\n",
|
42
|
+
"my_secret\n",
|
43
|
+
"my_code\n"
|
44
|
+
)
|
45
|
+
end
|
46
|
+
|
47
|
+
describe "#run" do
|
48
|
+
let!(:result) { subject.run }
|
49
|
+
|
50
|
+
it "clears the screen" do
|
51
|
+
expect(Kernel).to have_received(:system).with("clear")
|
52
|
+
end
|
53
|
+
|
54
|
+
it "requests client_id" do
|
55
|
+
expect(highline).to have_received(:ask).with("client_id: ")
|
56
|
+
end
|
57
|
+
|
58
|
+
it "requests client_secret" do
|
59
|
+
expect(highline).to have_received(:ask).with("client_secret: ")
|
60
|
+
end
|
61
|
+
|
62
|
+
it "displays the authorization URL" do
|
63
|
+
expect(Kernel).
|
64
|
+
to have_received(:puts).
|
65
|
+
with(/#{authorization_url}/)
|
66
|
+
end
|
67
|
+
|
68
|
+
it "requests the success code" do
|
69
|
+
expect(highline).to have_received(:ask).with("success code: ")
|
70
|
+
end
|
71
|
+
|
72
|
+
it "requests an access_token via the code" do
|
73
|
+
expect(authorizer).to have_received(:get_and_store_credentials_from_code)
|
74
|
+
end
|
75
|
+
|
76
|
+
it "returns the credentials" do
|
77
|
+
expect(result).to match('"sentinel":"foo"')
|
78
|
+
end
|
79
|
+
|
80
|
+
it "includes the client_secret in the credentials" do
|
81
|
+
expect(result).to match('"client_secret":"my_secret"')
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
@@ -35,18 +35,21 @@ describe Imap::Backup::Configuration::List do
|
|
35
35
|
allow(Imap::Backup::Configuration::Store).
|
36
36
|
to receive(:exist?) { config_exists }
|
37
37
|
allow(Imap::Backup).to receive(:setup_logging)
|
38
|
-
subject.setup_logging
|
39
38
|
end
|
40
39
|
|
41
40
|
it "sets global logging level" do
|
42
|
-
expect(Imap::Backup).to
|
41
|
+
expect(Imap::Backup).to receive(:setup_logging).with(store)
|
42
|
+
|
43
|
+
subject.setup_logging
|
43
44
|
end
|
44
45
|
|
45
46
|
context "without a config" do
|
46
47
|
let(:config_exists) { false }
|
47
48
|
|
48
49
|
it "does nothing" do
|
49
|
-
expect(Imap::Backup).to_not
|
50
|
+
expect(Imap::Backup).to_not receive(:setup_logging).with(store)
|
51
|
+
|
52
|
+
subject.setup_logging
|
50
53
|
end
|
51
54
|
end
|
52
55
|
end
|
@@ -1,8 +1,28 @@
|
|
1
|
-
# rubocop:disable RSpec/NestedGroups
|
2
|
-
|
3
1
|
describe Imap::Backup::Configuration::Setup do
|
4
2
|
include HighLineTestHelpers
|
5
3
|
|
4
|
+
subject { described_class.new }
|
5
|
+
|
6
|
+
let(:normal) { {username: "account@example.com"} }
|
7
|
+
let(:accounts) { [normal] }
|
8
|
+
let(:store) do
|
9
|
+
instance_double(
|
10
|
+
Imap::Backup::Configuration::Store,
|
11
|
+
"accounts": accounts,
|
12
|
+
"path": "/base/path",
|
13
|
+
"save": nil,
|
14
|
+
"debug?": debug,
|
15
|
+
"debug=": nil,
|
16
|
+
"modified?": modified
|
17
|
+
)
|
18
|
+
end
|
19
|
+
let(:debug) { false }
|
20
|
+
let(:modified) { false }
|
21
|
+
let!(:highline_streams) { prepare_highline }
|
22
|
+
let(:input) { highline_streams[0] }
|
23
|
+
let(:output) { highline_streams[1] }
|
24
|
+
let(:gmail_imap_server) { "imap.gmail.com" }
|
25
|
+
|
6
26
|
describe "#initialize" do
|
7
27
|
context "without a config file" do
|
8
28
|
it "works" do
|
@@ -12,27 +32,6 @@ describe Imap::Backup::Configuration::Setup do
|
|
12
32
|
end
|
13
33
|
|
14
34
|
describe "#run" do
|
15
|
-
subject { described_class.new }
|
16
|
-
|
17
|
-
let(:normal) { {username: "account@example.com"} }
|
18
|
-
let(:accounts) { [normal] }
|
19
|
-
let(:store) do
|
20
|
-
instance_double(
|
21
|
-
Imap::Backup::Configuration::Store,
|
22
|
-
"accounts": accounts,
|
23
|
-
"path": "/base/path",
|
24
|
-
"save": nil,
|
25
|
-
"debug?": debug,
|
26
|
-
"debug=": nil,
|
27
|
-
"modified?": modified
|
28
|
-
)
|
29
|
-
end
|
30
|
-
let(:debug) { false }
|
31
|
-
let(:modified) { false }
|
32
|
-
let!(:highline_streams) { prepare_highline }
|
33
|
-
let(:input) { highline_streams[0] }
|
34
|
-
let(:output) { highline_streams[1] }
|
35
|
-
|
36
35
|
before do
|
37
36
|
allow(Imap::Backup::Configuration::Store).to receive(:new) { store }
|
38
37
|
allow(Imap::Backup).to receive(:setup_logging)
|
@@ -52,15 +51,15 @@ describe Imap::Backup::Configuration::Setup do
|
|
52
51
|
end
|
53
52
|
|
54
53
|
it "clears the screen" do
|
55
|
-
|
54
|
+
expect(Kernel).to receive(:system).with("clear")
|
56
55
|
|
57
|
-
|
56
|
+
subject.run
|
58
57
|
end
|
59
58
|
|
60
59
|
it "updates logging status" do
|
61
|
-
|
60
|
+
expect(Imap::Backup).to receive(:setup_logging)
|
62
61
|
|
63
|
-
|
62
|
+
subject.run
|
64
63
|
end
|
65
64
|
|
66
65
|
describe "listing" do
|
@@ -97,43 +96,66 @@ describe Imap::Backup::Configuration::Setup do
|
|
97
96
|
before do
|
98
97
|
allow(input).to receive(:gets).and_return("1\n", "exit\n")
|
99
98
|
allow(Imap::Backup::Configuration::Asker).to receive(:email).
|
100
|
-
with(no_args)
|
99
|
+
with(no_args) { "new@example.com" }
|
101
100
|
allow(Imap::Backup::Configuration::Account).to receive(:new).
|
102
|
-
with(store, normal, anything)
|
103
|
-
|
104
|
-
subject.run
|
101
|
+
with(store, normal, anything) { account }
|
105
102
|
end
|
106
103
|
|
107
104
|
it "edits the account" do
|
108
|
-
expect(account).to
|
105
|
+
expect(account).to receive(:run)
|
106
|
+
|
107
|
+
subject.run
|
109
108
|
end
|
110
109
|
end
|
111
110
|
|
112
111
|
context "when adding accounts" do
|
113
112
|
let(:blank_account) do
|
114
113
|
{
|
115
|
-
username:
|
114
|
+
username: added_email,
|
116
115
|
password: "",
|
117
|
-
local_path:
|
116
|
+
local_path: local_path,
|
118
117
|
folders: []
|
119
118
|
}
|
120
119
|
end
|
121
120
|
let(:account) do
|
122
121
|
instance_double(Imap::Backup::Configuration::Account, run: nil)
|
123
122
|
end
|
123
|
+
let(:added_email) { "new@example.com" }
|
124
|
+
let(:local_path) { "/base/path/new_example.com" }
|
124
125
|
|
125
126
|
before do
|
126
127
|
allow(input).to receive(:gets).and_return("add\n", "exit\n")
|
127
128
|
allow(Imap::Backup::Configuration::Asker).to receive(:email).
|
128
|
-
with(no_args)
|
129
|
+
with(no_args) { added_email }
|
129
130
|
allow(Imap::Backup::Configuration::Account).to receive(:new).
|
130
|
-
with(store,
|
131
|
+
with(store, anything, anything) { account }
|
131
132
|
|
132
133
|
subject.run
|
133
134
|
end
|
134
135
|
|
135
|
-
it "
|
136
|
-
expect(accounts[1]).to eq(
|
136
|
+
it "sets username" do
|
137
|
+
expect(accounts[1][:username]).to eq(added_email)
|
138
|
+
end
|
139
|
+
|
140
|
+
it "sets blank password" do
|
141
|
+
expect(accounts[1][:password]).to eq("")
|
142
|
+
end
|
143
|
+
|
144
|
+
it "sets local_path" do
|
145
|
+
expect(accounts[1][:local_path]).to eq(local_path)
|
146
|
+
end
|
147
|
+
|
148
|
+
it "sets folders" do
|
149
|
+
expect(accounts[1][:folders]).to eq([])
|
150
|
+
end
|
151
|
+
|
152
|
+
context "when the account is a GMail account" do
|
153
|
+
let(:added_email) { "new@gmail.com" }
|
154
|
+
let(:local_path) { "/base/path/new_gmail.com" }
|
155
|
+
|
156
|
+
it "sets the server" do
|
157
|
+
expect(accounts[1][:server]).to eq(gmail_imap_server)
|
158
|
+
end
|
137
159
|
end
|
138
160
|
|
139
161
|
it "doesn't flag the unedited account as modified" do
|
@@ -145,20 +167,25 @@ describe Imap::Backup::Configuration::Setup do
|
|
145
167
|
context "when debug logging is disabled" do
|
146
168
|
before do
|
147
169
|
allow(input).to receive(:gets).and_return("start\n", "exit\n")
|
148
|
-
subject.run
|
149
170
|
end
|
150
171
|
|
151
172
|
it "shows a menu item" do
|
173
|
+
subject.run
|
174
|
+
|
152
175
|
expect(output.string).to include("start logging")
|
153
176
|
end
|
154
177
|
|
155
178
|
context "when selected" do
|
156
179
|
it "sets the debug flag" do
|
157
|
-
expect(store).to
|
180
|
+
expect(store).to receive(:debug=).with(true)
|
181
|
+
|
182
|
+
subject.run
|
158
183
|
end
|
159
184
|
|
160
185
|
it "updates logging status" do
|
161
|
-
expect(Imap::Backup).to
|
186
|
+
expect(Imap::Backup).to receive(:setup_logging).twice
|
187
|
+
|
188
|
+
subject.run
|
162
189
|
end
|
163
190
|
end
|
164
191
|
end
|
@@ -168,10 +195,11 @@ describe Imap::Backup::Configuration::Setup do
|
|
168
195
|
|
169
196
|
before do
|
170
197
|
allow(input).to receive(:gets).and_return("stop\n", "exit\n")
|
171
|
-
subject.run
|
172
198
|
end
|
173
199
|
|
174
200
|
it "shows a menu item" do
|
201
|
+
subject.run
|
202
|
+
|
175
203
|
expect(output.string).to include("stop logging")
|
176
204
|
end
|
177
205
|
|
@@ -181,11 +209,15 @@ describe Imap::Backup::Configuration::Setup do
|
|
181
209
|
end
|
182
210
|
|
183
211
|
it "unsets the debug flag" do
|
184
|
-
expect(store).to
|
212
|
+
expect(store).to receive(:debug=).with(false)
|
213
|
+
|
214
|
+
subject.run
|
185
215
|
end
|
186
216
|
|
187
217
|
it "updates logging status" do
|
188
|
-
expect(Imap::Backup).to
|
218
|
+
expect(Imap::Backup).to receive(:setup_logging).twice
|
219
|
+
|
220
|
+
subject.run
|
189
221
|
end
|
190
222
|
end
|
191
223
|
end
|
@@ -193,45 +225,48 @@ describe Imap::Backup::Configuration::Setup do
|
|
193
225
|
|
194
226
|
context "when 'save' is selected" do
|
195
227
|
before do
|
196
|
-
allow(input).to receive(:gets)
|
197
|
-
subject.run
|
228
|
+
allow(input).to receive(:gets) { "save\n" }
|
198
229
|
end
|
199
230
|
|
200
231
|
it "exits" do
|
201
232
|
# N.B. this will hang forever if save does not cause an exit
|
233
|
+
subject.run
|
202
234
|
end
|
203
235
|
|
204
236
|
it "saves the configuration" do
|
205
|
-
expect(store).to
|
237
|
+
expect(store).to receive(:save)
|
238
|
+
|
239
|
+
subject.run
|
206
240
|
end
|
207
241
|
end
|
208
242
|
|
209
243
|
context "when 'exit without saving' is selected" do
|
210
244
|
before do
|
211
|
-
allow(input).to receive(:gets)
|
212
|
-
|
213
|
-
subject.run
|
245
|
+
allow(input).to receive(:gets) { "exit\n" }
|
214
246
|
end
|
215
247
|
|
216
248
|
it "exits" do
|
217
249
|
# N.B. this will hang forever if quit does not cause an exit
|
250
|
+
subject.run
|
218
251
|
end
|
219
252
|
|
220
253
|
context "when the configuration is modified" do
|
221
254
|
let(:modified) { true }
|
222
255
|
|
223
256
|
it "doesn't save the configuration" do
|
224
|
-
expect(store).to_not
|
257
|
+
expect(store).to_not receive(:save)
|
258
|
+
|
259
|
+
subject.run
|
225
260
|
end
|
226
261
|
end
|
227
262
|
|
228
263
|
context "when the configuration isn't modified" do
|
229
264
|
it "doesn't save the configuration" do
|
230
|
-
expect(store).to_not
|
265
|
+
expect(store).to_not receive(:save)
|
266
|
+
|
267
|
+
subject.run
|
231
268
|
end
|
232
269
|
end
|
233
270
|
end
|
234
271
|
end
|
235
272
|
end
|
236
|
-
|
237
|
-
# rubocop:enable RSpec/NestedGroups
|
@@ -20,11 +20,11 @@ describe Imap::Backup::Configuration::Store do
|
|
20
20
|
allow(File).to receive(:exist?).and_call_original
|
21
21
|
allow(File).to receive(:exist?).with(file_path) { file_exists }
|
22
22
|
allow(Imap::Backup::Utils).
|
23
|
-
to receive(:stat).with(directory)
|
23
|
+
to receive(:stat).with(directory) { 0o700 }
|
24
24
|
allow(Imap::Backup::Utils).
|
25
|
-
to receive(:stat).with(file_path)
|
26
|
-
allow(Imap::Backup::Utils).to receive(:check_permissions)
|
27
|
-
allow(File).to receive(:read).with(file_path)
|
25
|
+
to receive(:stat).with(file_path) { 0o600 }
|
26
|
+
allow(Imap::Backup::Utils).to receive(:check_permissions) { nil }
|
27
|
+
allow(File).to receive(:read).with(file_path) { configuration }
|
28
28
|
end
|
29
29
|
|
30
30
|
describe ".exist?" do
|
@@ -140,19 +140,19 @@ describe Imap::Backup::Configuration::Store do
|
|
140
140
|
allow(FileUtils).to receive(:mkdir)
|
141
141
|
allow(FileUtils).to receive(:chmod)
|
142
142
|
allow(File).to receive(:open).with(file_path, "w").and_yield(file)
|
143
|
-
allow(JSON).to receive(:pretty_generate)
|
143
|
+
allow(JSON).to receive(:pretty_generate) { "JSON output" }
|
144
144
|
end
|
145
145
|
|
146
146
|
it "creates the config directory" do
|
147
|
-
|
147
|
+
expect(FileUtils).to receive(:mkdir).with(directory)
|
148
148
|
|
149
|
-
|
149
|
+
subject.save
|
150
150
|
end
|
151
151
|
|
152
152
|
it "saves the configuration" do
|
153
|
-
|
153
|
+
expect(file).to receive(:write).with("JSON output")
|
154
154
|
|
155
|
-
|
155
|
+
subject.save
|
156
156
|
end
|
157
157
|
|
158
158
|
context "when accounts are modified" do
|
@@ -164,7 +164,9 @@ describe Imap::Backup::Configuration::Store do
|
|
164
164
|
expected = Marshal.load(Marshal.dump(data))
|
165
165
|
expected[:accounts][0].delete(:modified)
|
166
166
|
|
167
|
-
expect(JSON).to
|
167
|
+
expect(JSON).to receive(:pretty_generate).with(expected)
|
168
|
+
|
169
|
+
subject.save
|
168
170
|
end
|
169
171
|
end
|
170
172
|
|
@@ -176,21 +178,21 @@ describe Imap::Backup::Configuration::Store do
|
|
176
178
|
]
|
177
179
|
end
|
178
180
|
|
179
|
-
before { subject.save }
|
180
|
-
|
181
181
|
it "does not save them" do
|
182
182
|
expected = Marshal.load(Marshal.dump(data))
|
183
183
|
expected[:accounts].pop
|
184
184
|
|
185
|
-
expect(JSON).to
|
185
|
+
expect(JSON).to receive(:pretty_generate).with(expected)
|
186
|
+
|
187
|
+
subject.save
|
186
188
|
end
|
187
189
|
end
|
188
190
|
|
189
191
|
context "when file permissions are too open" do
|
190
|
-
before { subject.save }
|
191
|
-
|
192
192
|
it "sets them to 0600" do
|
193
|
-
expect(FileUtils).to
|
193
|
+
expect(FileUtils).to receive(:chmod).with(0o600, file_path)
|
194
|
+
|
195
|
+
subject.save
|
194
196
|
end
|
195
197
|
end
|
196
198
|
|