imap-backup 6.0.0.rc2 → 6.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (108) hide show
  1. checksums.yaml +4 -4
  2. data/imap-backup.gemspec +5 -1
  3. data/lib/cli_coverage.rb +11 -11
  4. data/lib/email/provider/base.rb +2 -0
  5. data/lib/email/provider/unknown.rb +2 -0
  6. data/lib/email/provider.rb +2 -0
  7. data/lib/imap/backup/account/connection/backup_folders.rb +27 -0
  8. data/lib/imap/backup/account/connection/client_factory.rb +54 -0
  9. data/lib/imap/backup/account/connection/folder_names.rb +26 -0
  10. data/lib/imap/backup/account/connection.rb +11 -95
  11. data/lib/imap/backup/account/folder.rb +9 -6
  12. data/lib/imap/backup/account.rb +3 -5
  13. data/lib/imap/backup/cli/backup.rb +1 -3
  14. data/lib/imap/backup/cli/helpers.rb +24 -22
  15. data/lib/imap/backup/cli/local.rb +20 -13
  16. data/lib/imap/backup/cli/migrate.rb +4 -10
  17. data/lib/imap/backup/cli/restore.rb +8 -7
  18. data/lib/imap/backup/cli/setup.rb +10 -8
  19. data/lib/imap/backup/cli/stats.rb +78 -0
  20. data/lib/imap/backup/cli/status.rb +2 -2
  21. data/lib/imap/backup/cli/utils.rb +4 -6
  22. data/lib/imap/backup/cli.rb +24 -3
  23. data/lib/imap/backup/configuration.rb +9 -11
  24. data/lib/imap/backup/downloader.rb +48 -30
  25. data/lib/imap/backup/migrator.rb +5 -5
  26. data/lib/imap/backup/sanitizer.rb +3 -2
  27. data/lib/imap/backup/serializer/appender.rb +49 -0
  28. data/lib/imap/backup/serializer/message_enumerator.rb +29 -0
  29. data/lib/imap/backup/serializer/unused_name_finder.rb +27 -0
  30. data/lib/imap/backup/serializer.rb +24 -78
  31. data/lib/imap/backup/setup/account/header.rb +75 -0
  32. data/lib/imap/backup/setup/account.rb +14 -91
  33. data/lib/imap/backup/setup/asker.rb +4 -15
  34. data/lib/imap/backup/setup/backup_path.rb +41 -0
  35. data/lib/imap/backup/setup/email.rb +45 -0
  36. data/lib/imap/backup/setup/folder_chooser.rb +3 -3
  37. data/lib/imap/backup/setup/helpers.rb +1 -1
  38. data/lib/imap/backup/setup.rb +5 -4
  39. data/lib/imap/backup/thunderbird/mailbox_exporter.rb +39 -20
  40. data/lib/imap/backup/uploader.rb +46 -8
  41. data/lib/imap/backup/utils.rb +1 -1
  42. data/lib/imap/backup/version.rb +1 -1
  43. data/lib/imap/backup.rb +0 -1
  44. metadata +31 -134
  45. data/spec/features/backup_spec.rb +0 -100
  46. data/spec/features/configuration/minimal_configuration.rb +0 -15
  47. data/spec/features/configuration/missing_configuration.rb +0 -14
  48. data/spec/features/folders_spec.rb +0 -36
  49. data/spec/features/helper.rb +0 -2
  50. data/spec/features/local/list_accounts_spec.rb +0 -12
  51. data/spec/features/local/list_emails_spec.rb +0 -21
  52. data/spec/features/local/list_folders_spec.rb +0 -21
  53. data/spec/features/local/show_an_email_spec.rb +0 -34
  54. data/spec/features/migrate_spec.rb +0 -35
  55. data/spec/features/remote/list_account_folders_spec.rb +0 -16
  56. data/spec/features/restore_spec.rb +0 -162
  57. data/spec/features/status_spec.rb +0 -43
  58. data/spec/features/support/aruba.rb +0 -78
  59. data/spec/features/support/backup_directory.rb +0 -43
  60. data/spec/features/support/email_server.rb +0 -110
  61. data/spec/features/support/shared/connection_context.rb +0 -14
  62. data/spec/features/support/shared/message_fixtures.rb +0 -16
  63. data/spec/fixtures/connection.yml +0 -7
  64. data/spec/spec_helper.rb +0 -15
  65. data/spec/support/fixtures.rb +0 -11
  66. data/spec/support/higline_test_helpers.rb +0 -8
  67. data/spec/support/silence_logging.rb +0 -7
  68. data/spec/unit/email/mboxrd/message_spec.rb +0 -177
  69. data/spec/unit/email/provider/apple_mail_spec.rb +0 -7
  70. data/spec/unit/email/provider/base_spec.rb +0 -11
  71. data/spec/unit/email/provider/fastmail_spec.rb +0 -7
  72. data/spec/unit/email/provider/gmail_spec.rb +0 -7
  73. data/spec/unit/email/provider_spec.rb +0 -27
  74. data/spec/unit/imap/backup/account/connection_spec.rb +0 -433
  75. data/spec/unit/imap/backup/account/folder_spec.rb +0 -261
  76. data/spec/unit/imap/backup/account_spec.rb +0 -246
  77. data/spec/unit/imap/backup/cli/accounts_spec.rb +0 -58
  78. data/spec/unit/imap/backup/cli/backup_spec.rb +0 -19
  79. data/spec/unit/imap/backup/cli/folders_spec.rb +0 -39
  80. data/spec/unit/imap/backup/cli/helpers_spec.rb +0 -87
  81. data/spec/unit/imap/backup/cli/local_spec.rb +0 -100
  82. data/spec/unit/imap/backup/cli/migrate_spec.rb +0 -80
  83. data/spec/unit/imap/backup/cli/restore_spec.rb +0 -67
  84. data/spec/unit/imap/backup/cli/setup_spec.rb +0 -17
  85. data/spec/unit/imap/backup/cli/utils_spec.rb +0 -125
  86. data/spec/unit/imap/backup/cli_spec.rb +0 -93
  87. data/spec/unit/imap/backup/client/apple_mail_spec.rb +0 -9
  88. data/spec/unit/imap/backup/client/default_spec.rb +0 -22
  89. data/spec/unit/imap/backup/configuration_spec.rb +0 -238
  90. data/spec/unit/imap/backup/downloader_spec.rb +0 -96
  91. data/spec/unit/imap/backup/logger_spec.rb +0 -48
  92. data/spec/unit/imap/backup/migrator_spec.rb +0 -58
  93. data/spec/unit/imap/backup/sanitizer_spec.rb +0 -42
  94. data/spec/unit/imap/backup/serializer/directory_spec.rb +0 -37
  95. data/spec/unit/imap/backup/serializer/imap_spec.rb +0 -218
  96. data/spec/unit/imap/backup/serializer/mbox_enumerator_spec.rb +0 -45
  97. data/spec/unit/imap/backup/serializer/mbox_spec.rb +0 -101
  98. data/spec/unit/imap/backup/serializer_spec.rb +0 -296
  99. data/spec/unit/imap/backup/setup/account_spec.rb +0 -461
  100. data/spec/unit/imap/backup/setup/asker_spec.rb +0 -137
  101. data/spec/unit/imap/backup/setup/connection_tester_spec.rb +0 -51
  102. data/spec/unit/imap/backup/setup/folder_chooser_spec.rb +0 -146
  103. data/spec/unit/imap/backup/setup/helpers_spec.rb +0 -15
  104. data/spec/unit/imap/backup/setup_spec.rb +0 -301
  105. data/spec/unit/imap/backup/thunderbird/mailbox_exporter_spec.rb +0 -116
  106. data/spec/unit/imap/backup/uploader_spec.rb +0 -54
  107. data/spec/unit/imap/backup/utils_spec.rb +0 -92
  108. data/spec/unit/retry_on_error_spec.rb +0 -34
@@ -1,238 +0,0 @@
1
- require "json"
2
- require "os"
3
-
4
- # rubocop:disable RSpec/PredicateMatcher
5
-
6
- describe Imap::Backup::Configuration do
7
- let(:directory) { "/base/path" }
8
- let(:file_path) { File.join(directory, "/config.json") }
9
- let(:file_exists) { true }
10
- let(:directory_exists) { true }
11
- let(:debug) { true }
12
- let(:configuration) { data.to_json }
13
- let(:data) { {debug: debug, accounts: accounts.map(&:to_h)} }
14
- let(:accounts) { [account1, account2] }
15
- let(:account1) { Imap::Backup::Account.new({username: "username1"}) }
16
- let(:account2) { Imap::Backup::Account.new({username: "username2"}) }
17
-
18
- before do
19
- stub_const(
20
- "Imap::Backup::Configuration::CONFIGURATION_DIRECTORY", directory
21
- )
22
- allow(File).to receive(:directory?).with(directory) { directory_exists }
23
- allow(File).to receive(:exist?).and_call_original
24
- allow(File).to receive(:exist?).with(file_path) { file_exists }
25
- allow(Imap::Backup::Utils).
26
- to receive(:stat).with(directory) { 0o700 }
27
- allow(Imap::Backup::Utils).
28
- to receive(:stat).with(file_path) { 0o600 }
29
- allow(Imap::Backup::Utils).to receive(:check_permissions) { nil }
30
- allow(File).to receive(:read).with(file_path) { configuration }
31
- end
32
-
33
- describe ".exist?" do
34
- [true, false].each do |exists|
35
- state = exists ? "exists" : "doesn't exist"
36
- context "when the file #{state}" do
37
- let(:file_exists) { exists }
38
-
39
- it "returns #{exists}" do
40
- expect(described_class.exist?).to eq(file_exists)
41
- end
42
- end
43
- end
44
- end
45
-
46
- describe "#path" do
47
- it "is the directory containing the configuration file" do
48
- expect(subject.path).to eq(directory)
49
- end
50
- end
51
-
52
- describe "#modified?" do
53
- context "with modified accounts" do
54
- before { subject.accounts[0].username = "changed" }
55
-
56
- it "is true" do
57
- expect(subject.modified?).to be_truthy
58
- end
59
- end
60
-
61
- context "with accounts flagged 'delete'" do
62
- before { subject.accounts[0].mark_for_deletion }
63
-
64
- it "is true" do
65
- expect(subject.modified?).to be_truthy
66
- end
67
- end
68
-
69
- context "without accounts flagged 'modified'" do
70
- it "is false" do
71
- expect(subject.modified?).to be_falsey
72
- end
73
- end
74
- end
75
-
76
- describe "#debug?" do
77
- context "when the debug flag is true" do
78
- it "is true" do
79
- expect(subject.debug?).to be_truthy
80
- end
81
- end
82
-
83
- context "when the debug flag is false" do
84
- let(:debug) { false }
85
-
86
- it "is false" do
87
- expect(subject.debug?).to be_falsey
88
- end
89
- end
90
-
91
- context "when the debug flag is missing" do
92
- let(:data) { {accounts: accounts} }
93
-
94
- it "is false" do
95
- expect(subject.debug?).to be_falsey
96
- end
97
- end
98
-
99
- context "when the debug flag is neither true nor false" do
100
- let(:debug) { "hi" }
101
-
102
- it "is false" do
103
- expect(subject.debug?).to be_falsey
104
- end
105
- end
106
- end
107
-
108
- describe "#debug=" do
109
- before { subject.debug = debug }
110
-
111
- context "when the supplied value is true" do
112
- it "sets the flag to true" do
113
- expect(subject.debug?).to be_truthy
114
- end
115
- end
116
-
117
- context "when the supplied value is false" do
118
- let(:debug) { false }
119
-
120
- it "sets the flag to false" do
121
- expect(subject.debug?).to be_falsey
122
- end
123
- end
124
-
125
- context "when the supplied value is neither true nor false" do
126
- let(:debug) { "ciao" }
127
-
128
- it "sets the flag to false" do
129
- expect(subject.debug?).to be_falsey
130
- end
131
- end
132
- end
133
-
134
- describe "#save" do
135
- subject { described_class.new }
136
-
137
- let(:directory_exists) { false }
138
- let(:file) { instance_double(File, write: nil) }
139
-
140
- before do
141
- allow(FileUtils).to receive(:mkdir)
142
- allow(FileUtils).to receive(:chmod)
143
- allow(File).to receive(:open).with(file_path, "w").and_yield(file)
144
- allow(JSON).to receive(:pretty_generate) { "JSON output" }
145
- end
146
-
147
- it "creates the config directory" do
148
- expect(FileUtils).to receive(:mkdir).with(directory)
149
-
150
- subject.save
151
- end
152
-
153
- it "saves the configuration" do
154
- expect(file).to receive(:write).with("JSON output")
155
-
156
- subject.save
157
- end
158
-
159
- it "uses the Account#to_h method" do
160
- allow(subject.accounts[0]).to receive(:to_h) { "Account1" }
161
- allow(subject.accounts[1]).to receive(:to_h) { "Account2" }
162
-
163
- expect(JSON).to receive(:pretty_generate).
164
- with(hash_including({accounts: ["Account1", "Account2"]}))
165
-
166
- subject.save
167
- end
168
-
169
- context "when accounts are to be deleted" do
170
- let(:accounts) do
171
- [
172
- {name: "keep_me"},
173
- {name: "delete_me", delete: true}
174
- ]
175
- end
176
-
177
- before do
178
- allow(subject.accounts[0]).to receive(:to_h) { "Account1" }
179
- allow(subject.accounts[1]).to receive(:to_h) { "Account2" }
180
- subject.accounts[0].mark_for_deletion
181
- end
182
-
183
- it "does not save them" do
184
- expect(JSON).to receive(:pretty_generate).
185
- with(hash_including({accounts: ["Account2"]}))
186
-
187
- subject.save
188
- end
189
- end
190
-
191
- context "when file permissions are too open" do
192
- context "when on UNIX" do
193
- before do
194
- allow(OS).to receive(:windows?) { false }
195
- end
196
-
197
- it "sets them to 0600" do
198
- expect(FileUtils).to receive(:chmod).with(0o600, file_path)
199
-
200
- subject.save
201
- end
202
- end
203
- end
204
-
205
- context "when the configuration file is missing" do
206
- let(:file_exists) { false }
207
-
208
- it "doesn't fail" do
209
- expect do
210
- subject.save
211
- end.to_not raise_error
212
- end
213
- end
214
-
215
- context "when on UNIX" do
216
- before do
217
- allow(OS).to receive(:windows?) { false }
218
- end
219
-
220
- context "when the config file permissions are too lax" do
221
- let(:file_exists) { true }
222
-
223
- before do
224
- allow(Imap::Backup::Utils).to receive(:check_permissions).
225
- with(file_path, 0o600).and_raise("Error")
226
- end
227
-
228
- it "fails" do
229
- expect do
230
- subject.save
231
- end.to raise_error(RuntimeError, "Error")
232
- end
233
- end
234
- end
235
- end
236
- end
237
-
238
- # rubocop:enable RSpec/PredicateMatcher
@@ -1,96 +0,0 @@
1
- describe Imap::Backup::Downloader do
2
- describe "#run" do
3
- subject { described_class.new(folder, serializer, **options) }
4
-
5
- let(:body) { "blah" }
6
- let(:folder) do
7
- instance_double(
8
- Imap::Backup::Account::Folder,
9
- fetch_multi: [{uid: "111", body: body}],
10
- name: "folder",
11
- uids: remote_uids
12
- )
13
- end
14
- let(:remote_uids) { %w(111 222 333) }
15
- let(:serializer) do
16
- instance_double(Imap::Backup::Serializer, append: nil, uids: local_uids)
17
- end
18
- let(:local_uids) { ["222"] }
19
- let(:options) { {} }
20
-
21
- context "with fetched messages" do
22
- specify "are saved" do
23
- expect(serializer).to receive(:append).with("111", body)
24
-
25
- subject.run
26
- end
27
- end
28
-
29
- context "with messages which are already present" do
30
- specify "are skipped" do
31
- expect(serializer).to_not receive(:append).with("222", anything)
32
-
33
- subject.run
34
- end
35
- end
36
-
37
- context "with failed fetches" do
38
- specify "are skipped" do
39
- allow(folder).to receive(:fetch_multi) { nil }
40
- expect(serializer).to_not receive(:append)
41
-
42
- subject.run
43
- end
44
- end
45
-
46
- context "when the block size is greater than one" do
47
- let(:remote_uids) { %w(111 999) }
48
- let(:local_uids) { [] }
49
- let(:options) { {multi_fetch_size: 2} }
50
-
51
- context "when the first fetch fails" do
52
- before do
53
- allow(folder).to receive(:fetch_multi).with(["111", "999"]) { nil }
54
- allow(folder).to receive(:fetch_multi).with(["111"]).
55
- and_return([{uid: "111", body: body}]).
56
- and_return([{uid: "999", body: body}])
57
-
58
- subject.run
59
- end
60
-
61
- it "retries fetching messages singly" do
62
- expect(serializer).to have_received(:append).with("111", body)
63
- expect(serializer).to have_received(:append).with("999", body)
64
- end
65
- end
66
- end
67
-
68
- context "when no body is returned by the fetch" do
69
- let(:remote_uids) { %w(111) }
70
-
71
- before do
72
- allow(folder).to receive(:fetch_multi).with(["111"]) { [{uid: "111", body: nil}] }
73
-
74
- subject.run
75
- end
76
-
77
- it "skips the append" do
78
- expect(serializer).to_not have_received(:append)
79
- end
80
- end
81
-
82
- context "when the UID is not returned by the fetch" do
83
- let(:remote_uids) { %w(111) }
84
-
85
- before do
86
- allow(folder).to receive(:fetch_multi).with(["111"]) { [{uid: nil, body: body}] }
87
-
88
- subject.run
89
- end
90
-
91
- it "skips the append" do
92
- expect(serializer).to_not have_received(:append)
93
- end
94
- end
95
- end
96
- end
@@ -1,48 +0,0 @@
1
- require "net/imap"
2
-
3
- module Imap::Backup
4
- describe Logger do
5
- describe ".setup_logging" do
6
- let(:config) { instance_double(Configuration, debug?: debug) }
7
-
8
- around do |example|
9
- logger_previous = described_class.logger.level
10
- net_imap_previous = Net::IMAP.debug
11
- described_class.logger.level = 42
12
- Net::IMAP.debug = 42
13
- example.run
14
- Net::IMAP.debug = net_imap_previous
15
- described_class.logger.level = logger_previous
16
- end
17
-
18
- before do
19
- allow(Configuration).to receive(:new) { config }
20
- described_class.setup_logging
21
- end
22
-
23
- context "when config.debug?" do
24
- let(:debug) { true }
25
-
26
- it "sets logger level to debug" do
27
- expect(described_class.logger.level).to eq(::Logger::Severity::DEBUG)
28
- end
29
-
30
- it "sets the Net::IMAP debug flag" do
31
- expect(Net::IMAP.debug).to be_a(TrueClass)
32
- end
33
- end
34
-
35
- context "when not config.debug?" do
36
- let(:debug) { false }
37
-
38
- it "sets logger level to error" do
39
- expect(described_class.logger.level).to eq(::Logger::Severity::ERROR)
40
- end
41
-
42
- it "doesn't set the Net::IMAP debug flag" do
43
- expect(Net::IMAP.debug).to be_a(FalseClass)
44
- end
45
- end
46
- end
47
- end
48
- end
@@ -1,58 +0,0 @@
1
- require "imap/backup/migrator"
2
-
3
- module Imap::Backup
4
- RSpec.describe Migrator do
5
- subject { described_class.new(serializer, folder, reset: reset) }
6
-
7
- let(:serializer) { instance_double(Serializer, uids: [1]) }
8
- let(:folder) do
9
- instance_double(
10
- Account::Folder,
11
- append: nil, clear: nil, create: nil, name: "name", uids: folder_uids
12
- )
13
- end
14
- let(:folder_uids) { [] }
15
- let(:reset) { false }
16
- let(:messages) { [[1, message]] }
17
- let(:message) { instance_double(Email::Mboxrd::Message, supplied_body: body) }
18
- let(:body) { "body" }
19
-
20
- before do
21
- allow(serializer).to receive(:each_message) do
22
- messages.enum_for(:each)
23
- end
24
- end
25
-
26
- it "creates the folder" do
27
- subject.run
28
-
29
- expect(folder).to have_received(:create)
30
- end
31
-
32
- it "uploads messages" do
33
- subject.run
34
-
35
- expect(folder).to have_received(:append).with(message)
36
- end
37
-
38
- context "when the folder is not empty" do
39
- let(:folder_uids) { [99] }
40
-
41
- it "fails" do
42
- expect do
43
- subject.run
44
- end.to raise_error(RuntimeError, /The destination folder 'name' is not empty/)
45
- end
46
-
47
- context "when `reset` is true" do
48
- let(:reset) { true }
49
-
50
- it "clears the folder" do
51
- subject.run
52
-
53
- expect(folder).to have_received(:clear)
54
- end
55
- end
56
- end
57
- end
58
- end
@@ -1,42 +0,0 @@
1
- module Imap::Backup
2
- describe Sanitizer do
3
- require "stringio"
4
-
5
- subject { described_class.new(output) }
6
-
7
- let(:output) { StringIO.new }
8
-
9
- describe "#puts" do
10
- it "delegates to output" do
11
- subject.puts("x")
12
-
13
- expect(output.string).to eq("x\n")
14
- end
15
- end
16
-
17
- describe "#write" do
18
- it "delegates to output" do
19
- subject.write("x")
20
-
21
- expect(output.string).to eq("x")
22
- end
23
- end
24
-
25
- describe "#print" do
26
- it "removes passwords from complete lines of text" do
27
- subject.print("C: RUBY99 LOGIN xx) secret!!!!\netc")
28
-
29
- expect(output.string).to eq("C: RUBY99 LOGIN xx) [PASSWORD REDACTED]\n")
30
- end
31
- end
32
-
33
- describe "#flush" do
34
- it "sanitizes remaining text" do
35
- subject.print("before\nC: RUBY99 LOGIN xx) secret!!!!")
36
- subject.flush
37
-
38
- expect(output.string).to eq("before\nC: RUBY99 LOGIN xx) [PASSWORD REDACTED]\n")
39
- end
40
- end
41
- end
42
- end
@@ -1,37 +0,0 @@
1
- module Imap::Backup
2
- describe Serializer::Directory do
3
- subject { described_class.new("path", "relative") }
4
-
5
- let(:windows) { false }
6
-
7
- before do
8
- allow(File).to receive(:directory?) { false }
9
- allow(Utils).to receive(:make_folder)
10
- allow(OS).to receive(:windows?) { windows }
11
- allow(Utils).to receive(:mode) { 0o600 }
12
- allow(FileUtils).to receive(:chmod)
13
-
14
- subject.ensure_exists
15
- end
16
-
17
- describe "#ensure_exists" do
18
- context "when the directory doesn't exist" do
19
- it "makes the directory" do
20
- expect(Utils).to have_received(:make_folder)
21
- end
22
- end
23
-
24
- it "sets permissions" do
25
- expect(FileUtils).to have_received(:chmod)
26
- end
27
-
28
- context "when on Windows" do
29
- let(:windows) { true }
30
-
31
- it "doesn't set permissions" do
32
- expect(FileUtils).to_not have_received(:chmod)
33
- end
34
- end
35
- end
36
- end
37
- end