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