imap-backup 2.1.1 → 3.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (77) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +5 -4
  3. data/.rubocop_todo.yml +29 -11
  4. data/.travis.yml +1 -1
  5. data/README.md +10 -13
  6. data/bin/imap-backup +5 -2
  7. data/docs/01-credentials-screen.png +0 -0
  8. data/docs/02-new-project.png +0 -0
  9. data/docs/03-initial-credentials-for-project.png +0 -0
  10. data/docs/04-credential-type-selection.png +0 -0
  11. data/docs/05-cant-create-without-consent-setup.png +0 -0
  12. data/docs/06-user-type-selection.png +0 -0
  13. data/docs/07-consent-screen-form.png +0 -0
  14. data/docs/08-app-scopes.png +0 -0
  15. data/docs/09-scope-selection.png +0 -0
  16. data/docs/10-updated-app-scopes.png +0 -0
  17. data/docs/11-test-users.png +0 -0
  18. data/docs/12-add-users.png +0 -0
  19. data/docs/13-create-oauth-client.png +0 -0
  20. data/docs/14-application-details.png +0 -0
  21. data/docs/16-initial-menu.png +0 -0
  22. data/docs/17-inputting-the-email-address.png +0 -0
  23. data/docs/18-choose-password.png +0 -0
  24. data/docs/19-supply-client-info.png +0 -0
  25. data/docs/20-choose-gmail-account.png +0 -0
  26. data/docs/21-accept-warnings.png +0 -0
  27. data/docs/22-grant-access.png +0 -0
  28. data/docs/24-confirm-choices.png +0 -0
  29. data/docs/25-success-code.png +0 -0
  30. data/docs/26-type-code-into-imap-backup.png +0 -0
  31. data/docs/27-success.png +0 -0
  32. data/docs/setting-up-gmail.md +166 -0
  33. data/imap-backup.gemspec +3 -9
  34. data/lib/email/mboxrd/message.rb +4 -3
  35. data/lib/email/provider.rb +3 -1
  36. data/lib/gmail/authenticator.rb +160 -0
  37. data/lib/google/auth/stores/in_memory_token_store.rb +9 -0
  38. data/lib/imap/backup.rb +2 -1
  39. data/lib/imap/backup/account/connection.rb +59 -34
  40. data/lib/imap/backup/account/folder.rb +10 -1
  41. data/lib/imap/backup/configuration/account.rb +9 -1
  42. data/lib/imap/backup/configuration/gmail_oauth2.rb +82 -0
  43. data/lib/imap/backup/configuration/setup.rb +4 -1
  44. data/lib/imap/backup/serializer/mbox.rb +4 -0
  45. data/lib/imap/backup/serializer/mbox_enumerator.rb +1 -1
  46. data/lib/imap/backup/serializer/mbox_store.rb +20 -4
  47. data/lib/imap/backup/uploader.rb +10 -2
  48. data/lib/imap/backup/version.rb +5 -4
  49. data/spec/features/backup_spec.rb +3 -3
  50. data/spec/features/helper.rb +1 -1
  51. data/spec/features/restore_spec.rb +75 -27
  52. data/spec/features/support/backup_directory.rb +2 -2
  53. data/spec/features/support/email_server.rb +1 -3
  54. data/spec/features/support/shared/message_fixtures.rb +8 -0
  55. data/spec/spec_helper.rb +1 -1
  56. data/spec/support/fixtures.rb +1 -1
  57. data/spec/unit/email/mboxrd/message_spec.rb +2 -8
  58. data/spec/unit/email/provider_spec.rb +2 -2
  59. data/spec/unit/gmail/authenticator_spec.rb +138 -0
  60. data/spec/unit/google/auth/stores/in_memory_token_store_spec.rb +15 -0
  61. data/spec/unit/imap/backup/account/connection_spec.rb +157 -79
  62. data/spec/unit/imap/backup/account/folder_spec.rb +30 -20
  63. data/spec/unit/imap/backup/configuration/account_spec.rb +65 -46
  64. data/spec/unit/imap/backup/configuration/asker_spec.rb +20 -17
  65. data/spec/unit/imap/backup/configuration/connection_tester_spec.rb +6 -10
  66. data/spec/unit/imap/backup/configuration/folder_chooser_spec.rb +16 -10
  67. data/spec/unit/imap/backup/configuration/gmail_oauth2_spec.rb +84 -0
  68. data/spec/unit/imap/backup/configuration/list_spec.rb +6 -3
  69. data/spec/unit/imap/backup/configuration/setup_spec.rb +89 -54
  70. data/spec/unit/imap/backup/configuration/store_spec.rb +18 -16
  71. data/spec/unit/imap/backup/downloader_spec.rb +14 -14
  72. data/spec/unit/imap/backup/serializer/mbox_enumerator_spec.rb +6 -1
  73. data/spec/unit/imap/backup/serializer/mbox_spec.rb +62 -40
  74. data/spec/unit/imap/backup/serializer/mbox_store_spec.rb +94 -35
  75. data/spec/unit/imap/backup/uploader_spec.rb +23 -7
  76. data/spec/unit/imap/backup/utils_spec.rb +10 -9
  77. 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 have_received(:setup_logging).with(store)
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 have_received(:setup_logging).with(store)
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
- subject.run
54
+ expect(Kernel).to receive(:system).with("clear")
56
55
 
57
- expect(Kernel).to have_received(:system).with("clear")
56
+ subject.run
58
57
  end
59
58
 
60
59
  it "updates logging status" do
61
- subject.run
60
+ expect(Imap::Backup).to receive(:setup_logging)
62
61
 
63
- expect(Imap::Backup).to have_received(:setup_logging)
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).and_return("new@example.com")
99
+ with(no_args) { "new@example.com" }
101
100
  allow(Imap::Backup::Configuration::Account).to receive(:new).
102
- with(store, normal, anything).and_return(account)
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 have_received(:run)
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: "new@example.com",
114
+ username: added_email,
116
115
  password: "",
117
- local_path: "/base/path/new_example.com",
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).and_return("new@example.com")
129
+ with(no_args) { added_email }
129
130
  allow(Imap::Backup::Configuration::Account).to receive(:new).
130
- with(store, blank_account, anything).and_return(account)
131
+ with(store, anything, anything) { account }
131
132
 
132
133
  subject.run
133
134
  end
134
135
 
135
- it "adds account data" do
136
- expect(accounts[1]).to eq(blank_account)
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 have_received(:debug=).with(true)
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 have_received(:setup_logging).twice
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 have_received(:debug=).with(false)
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 have_received(:setup_logging).twice
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).and_return("save\n")
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 have_received(:save)
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).and_return("exit\n")
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 have_received(:save)
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 have_received(:save)
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).and_return(0o700)
23
+ to receive(:stat).with(directory) { 0o700 }
24
24
  allow(Imap::Backup::Utils).
25
- to receive(:stat).with(file_path).and_return(0o600)
26
- allow(Imap::Backup::Utils).to receive(:check_permissions).and_return(nil)
27
- allow(File).to receive(:read).with(file_path).and_return(configuration)
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).and_return("JSON output")
143
+ allow(JSON).to receive(:pretty_generate) { "JSON output" }
144
144
  end
145
145
 
146
146
  it "creates the config directory" do
147
- subject.save
147
+ expect(FileUtils).to receive(:mkdir).with(directory)
148
148
 
149
- expect(FileUtils).to have_received(:mkdir).with(directory)
149
+ subject.save
150
150
  end
151
151
 
152
152
  it "saves the configuration" do
153
- subject.save
153
+ expect(file).to receive(:write).with("JSON output")
154
154
 
155
- expect(file).to have_received(:write).with("JSON output")
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 have_received(:pretty_generate).with(expected)
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 have_received(:pretty_generate).with(expected)
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 have_received(:chmod).with(0o600, file_path)
193
+ expect(FileUtils).to receive(:chmod).with(0o600, file_path)
194
+
195
+ subject.save
194
196
  end
195
197
  end
196
198