imap-backup 5.0.0 → 6.0.0.rc2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (70) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +8 -7
  3. data/bin/imap-backup +4 -0
  4. data/docs/development.md +10 -4
  5. data/imap-backup.gemspec +2 -7
  6. data/lib/cli_coverage.rb +18 -0
  7. data/lib/imap/backup/account/connection.rb +7 -11
  8. data/lib/imap/backup/account/folder.rb +0 -16
  9. data/lib/imap/backup/account.rb +31 -11
  10. data/lib/imap/backup/cli/folders.rb +3 -3
  11. data/lib/imap/backup/cli/migrate.rb +3 -3
  12. data/lib/imap/backup/cli/restore.rb +20 -4
  13. data/lib/imap/backup/cli/utils.rb +2 -2
  14. data/lib/imap/backup/cli.rb +6 -7
  15. data/lib/imap/backup/configuration.rb +1 -11
  16. data/lib/imap/backup/downloader.rb +13 -9
  17. data/lib/imap/backup/serializer/directory.rb +37 -0
  18. data/lib/imap/backup/serializer/imap.rb +120 -0
  19. data/lib/imap/backup/serializer/mbox.rb +23 -94
  20. data/lib/imap/backup/serializer/mbox_enumerator.rb +2 -0
  21. data/lib/imap/backup/serializer.rb +180 -3
  22. data/lib/imap/backup/setup/account.rb +52 -29
  23. data/lib/imap/backup/setup/helpers.rb +1 -1
  24. data/lib/imap/backup/thunderbird/mailbox_exporter.rb +1 -1
  25. data/lib/imap/backup/version.rb +2 -2
  26. data/lib/imap/backup.rb +0 -1
  27. data/spec/features/backup_spec.rb +22 -29
  28. data/spec/features/restore_spec.rb +8 -6
  29. data/spec/features/support/aruba.rb +12 -3
  30. data/spec/features/support/backup_directory.rb +0 -4
  31. data/spec/features/support/email_server.rb +0 -1
  32. data/spec/spec_helper.rb +4 -9
  33. data/spec/unit/imap/backup/account/connection_spec.rb +36 -8
  34. data/spec/unit/imap/backup/account/folder_spec.rb +18 -16
  35. data/spec/unit/imap/backup/account_spec.rb +246 -0
  36. data/spec/unit/imap/backup/cli/accounts_spec.rb +12 -1
  37. data/spec/unit/imap/backup/cli/backup_spec.rb +19 -0
  38. data/spec/unit/imap/backup/cli/folders_spec.rb +39 -0
  39. data/spec/unit/imap/backup/cli/local_spec.rb +26 -7
  40. data/spec/unit/imap/backup/cli/migrate_spec.rb +80 -0
  41. data/spec/unit/imap/backup/cli/restore_spec.rb +67 -0
  42. data/spec/unit/imap/backup/cli/setup_spec.rb +17 -0
  43. data/spec/unit/imap/backup/cli/utils_spec.rb +68 -5
  44. data/spec/unit/imap/backup/cli_spec.rb +93 -0
  45. data/spec/unit/imap/backup/client/apple_mail_spec.rb +9 -0
  46. data/spec/unit/imap/backup/configuration_spec.rb +2 -2
  47. data/spec/unit/imap/backup/downloader_spec.rb +60 -8
  48. data/spec/unit/imap/backup/logger_spec.rb +1 -1
  49. data/spec/unit/imap/backup/migrator_spec.rb +1 -1
  50. data/spec/unit/imap/backup/sanitizer_spec.rb +42 -0
  51. data/spec/unit/imap/backup/serializer/directory_spec.rb +37 -0
  52. data/spec/unit/imap/backup/serializer/imap_spec.rb +218 -0
  53. data/spec/unit/imap/backup/serializer/mbox_spec.rb +62 -183
  54. data/spec/unit/imap/backup/serializer_spec.rb +296 -0
  55. data/spec/unit/imap/backup/setup/account_spec.rb +120 -25
  56. data/spec/unit/imap/backup/setup/helpers_spec.rb +15 -0
  57. data/spec/unit/imap/backup/thunderbird/mailbox_exporter_spec.rb +116 -0
  58. data/spec/unit/imap/backup/uploader_spec.rb +1 -1
  59. data/spec/unit/retry_on_error_spec.rb +34 -0
  60. metadata +44 -37
  61. data/lib/imap/backup/serializer/mbox_store.rb +0 -217
  62. data/lib/thunderbird/install.rb +0 -16
  63. data/lib/thunderbird/local_folder.rb +0 -65
  64. data/lib/thunderbird/profile.rb +0 -30
  65. data/lib/thunderbird/profiles.rb +0 -71
  66. data/lib/thunderbird/subdirectory.rb +0 -93
  67. data/lib/thunderbird/subdirectory_placeholder.rb +0 -21
  68. data/lib/thunderbird.rb +0 -14
  69. data/spec/gather_rspec_coverage.rb +0 -1
  70. data/spec/unit/imap/backup/serializer/mbox_store_spec.rb +0 -329
@@ -0,0 +1,42 @@
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
@@ -0,0 +1,37 @@
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
@@ -0,0 +1,218 @@
1
+ module Imap::Backup
2
+ describe Serializer::Imap do
3
+ subject { described_class.new(folder_path) }
4
+
5
+ let(:folder_path) { "folder_path" }
6
+ let(:pathname) { "folder_path.imap" }
7
+ let(:exists) { true }
8
+ let(:existing) { {uid_validity: 99, uids: [42]} }
9
+ let(:file) { instance_double(File, write: nil) }
10
+
11
+ before do
12
+ allow(File).to receive(:exist?).and_call_original
13
+ allow(File).to receive(:exist?).with(pathname) { exists }
14
+ allow(File).to receive(:open).and_call_original
15
+ allow(File).to receive(:open).with(pathname, "w").and_yield(file)
16
+ allow(File).to receive(:read).with(pathname) { existing.to_json }
17
+ end
18
+
19
+ describe "loading the metadata file" do
20
+ context "when it is malformed" do
21
+ before do
22
+ allow(File).to receive(:read).with(pathname).and_raise(JSON::ParserError)
23
+ end
24
+
25
+ it "ignores the file" do
26
+ subject.uid_validity
27
+
28
+ expect(subject.uids).to eq([])
29
+ end
30
+ end
31
+ end
32
+
33
+ describe "#append" do
34
+ context "when the metadata file exists" do
35
+ before { subject.append(123) }
36
+
37
+ it "loads the existing metadata" do
38
+ expect(subject.uids).to include(42)
39
+ end
40
+
41
+ it "appends the UID" do
42
+ expect(subject.uids).to include(123)
43
+ end
44
+
45
+ it "saves the file" do
46
+ expect(file).to have_received(:write).
47
+ with(/"uids":\[42,123\]/)
48
+ end
49
+ end
50
+
51
+ context "when the metadata file doesn't exist" do
52
+ let(:exists) { false }
53
+
54
+ context "when the uid_validity is set" do
55
+ before do
56
+ subject.uid_validity = 999
57
+ end
58
+
59
+ it "appends the UID" do
60
+ subject.append(123)
61
+
62
+ expect(subject.uids).to include(123)
63
+ end
64
+
65
+ it "saves the file" do
66
+ subject.append(123)
67
+
68
+ expect(file).to have_received(:write).
69
+ with(/"uids":\[123\]/)
70
+ end
71
+ end
72
+
73
+ context "when the uid_validity is not set" do
74
+ it "fails" do
75
+ expect do
76
+ subject.append(123)
77
+ end.to raise_error(RuntimeError, /without a uid_validity/)
78
+ end
79
+ end
80
+ end
81
+ end
82
+
83
+ describe "#exist?" do
84
+ context "when the metadata file exists" do
85
+ it "is true" do
86
+ expect(subject.exist?).to be true
87
+ end
88
+ end
89
+
90
+ context "when the metadata file doesn't exist" do
91
+ let(:exists) { false }
92
+
93
+ it "is false" do
94
+ expect(subject.exist?).to be false
95
+ end
96
+ end
97
+ end
98
+
99
+ describe "#include?" do
100
+ it "loads the existing metadata" do
101
+ subject.include?(42)
102
+
103
+ expect(File).to have_received(:read).with(pathname)
104
+ end
105
+
106
+ context "when there is a matching UID" do
107
+ it "is true" do
108
+ expect(subject.include?(42)).to be true
109
+ end
110
+ end
111
+
112
+ context "when there isn't a matching UID" do
113
+ it "is false" do
114
+ expect(subject.include?(99)).to be false
115
+ end
116
+ end
117
+ end
118
+
119
+ describe "#index" do
120
+ it "loads the existing metadata" do
121
+ subject.include?(42)
122
+
123
+ expect(File).to have_received(:read).with(pathname)
124
+ end
125
+
126
+ context "when there is a matching UID" do
127
+ it "returns the index of the matcing UID" do
128
+ expect(subject.index(42)).to eq(0)
129
+ end
130
+ end
131
+
132
+ context "when there isn't a matching UID" do
133
+ it "is nil" do
134
+ expect(subject.index(99)).to be nil
135
+ end
136
+ end
137
+ end
138
+
139
+ describe "#rename" do
140
+ before do
141
+ allow(File).to receive(:rename)
142
+
143
+ subject.rename("new_path")
144
+ end
145
+
146
+ context "when the metadata file exists" do
147
+ it "sets the folder_path" do
148
+ expect(subject.folder_path).to eq("new_path")
149
+ end
150
+
151
+ it "renames the metadata file" do
152
+ expect(File).to have_received(:rename).with(pathname, "new_path.imap")
153
+ end
154
+ end
155
+
156
+ context "when the metadata file doesn't exist" do
157
+ let(:exists) { false }
158
+
159
+ it "sets the folder_path" do
160
+ expect(subject.folder_path).to eq("new_path")
161
+ end
162
+
163
+ it "doesn't try to rename the metadata file" do
164
+ expect(File).to_not have_received(:rename)
165
+ end
166
+ end
167
+ end
168
+
169
+ describe "#uid_validity" do
170
+ it "returns the uid_validity" do
171
+ expect(subject.uid_validity).to eq(99)
172
+ end
173
+ end
174
+
175
+ describe "#uid_validity=" do
176
+ before { subject.uid_validity = 567 }
177
+
178
+ it "updates the uid_validity" do
179
+ expect(subject.uid_validity).to eq(567)
180
+ end
181
+
182
+ it "saves the file" do
183
+ expect(file).to have_received(:write).
184
+ with(/"uid_validity":567/)
185
+ end
186
+
187
+ context "when no metadata file exists" do
188
+ let(:exists) { false }
189
+
190
+ it "saves an empty list of UIDs" do
191
+ expect(file).to have_received(:write).
192
+ with(/"uids":\[\]/)
193
+ end
194
+ end
195
+ end
196
+
197
+ describe "#update_uid" do
198
+ before { subject.update_uid(42, 57) }
199
+
200
+ it "sets the UID" do
201
+ expect(subject.uids).to eq([57])
202
+ end
203
+
204
+ it "saves the file" do
205
+ expect(file).to have_received(:write).
206
+ with(/"uids":\[57\]/)
207
+ end
208
+
209
+ context "when the UID is not present" do
210
+ let(:existing) { {uid_validity: 99, uids: [2]} }
211
+
212
+ it "doesn't save the file" do
213
+ expect(file).to_not have_received(:write)
214
+ end
215
+ end
216
+ end
217
+ end
218
+ end
@@ -1,222 +1,101 @@
1
- describe Imap::Backup::Serializer::Mbox do
2
- subject { described_class.new(base_path, imap_folder) }
3
-
4
- let(:base_path) { "/base/path" }
5
- let(:store) do
6
- instance_double(
7
- Imap::Backup::Serializer::MboxStore,
8
- add: nil,
9
- rename: nil,
10
- uids: nil,
11
- uid_validity: existing_uid_validity,
12
- "uid_validity=": nil,
13
- update_uid: nil
14
- )
15
- end
16
- let(:imap_folder) { "folder" }
17
- let(:permissions) { 0o700 }
18
- let(:dir_exists) { true }
19
- let(:existing_uid_validity) { nil }
20
-
21
- before do
22
- allow(Imap::Backup::Utils).to receive(:make_folder)
23
- allow(Imap::Backup::Utils).to receive(:mode) { permissions }
24
- allow(Imap::Backup::Utils).to receive(:check_permissions) { true }
25
- allow(File).to receive(:directory?) { dir_exists }
26
- allow(FileUtils).to receive(:chmod)
27
- allow(Imap::Backup::Serializer::MboxStore).to receive(:new) { store }
28
- end
29
-
30
- describe "folder path" do
31
- context "when it has multiple elements" do
32
- let(:imap_folder) { "folder/path" }
33
-
34
- context "when the containing directory is missing" do
35
- let(:dir_exists) { false }
36
-
37
- it "is created" do
38
- expect(Imap::Backup::Utils).to receive(:make_folder).
39
- with(base_path, File.dirname(imap_folder), 0o700)
40
-
41
- subject.uids
42
- end
43
- end
1
+ module Imap::Backup
2
+ describe Serializer::Mbox do
3
+ subject { described_class.new(folder_path) }
4
+
5
+ let(:folder_path) { "folder_path" }
6
+ let(:pathname) { "folder_path.mbox" }
7
+ let(:exists) { true }
8
+ let(:file) { instance_double(File, truncate: nil, write: nil) }
9
+
10
+ before do
11
+ allow(File).to receive(:exist?).and_call_original
12
+ allow(File).to receive(:exist?).with(pathname) { exists }
13
+ allow(File).to receive(:open).with(pathname, "ab").and_yield(file)
14
+ allow(File).to receive(:read).with(pathname) { existing.to_json }
44
15
  end
45
16
 
46
- context "when permissions are incorrect" do
47
- let(:permissions) { 0o777 }
48
-
49
- it "corrects them" do
50
- path = File.expand_path(File.join(base_path, File.dirname(imap_folder)))
51
- expect(FileUtils).to receive(:chmod).with(0o700, path)
17
+ describe "#append" do
18
+ it "appends the message" do
19
+ subject.append("message")
52
20
 
53
- subject.uids
21
+ expect(file).to have_received(:write).with("message")
54
22
  end
55
23
  end
56
24
 
57
- context "when permissons are correct" do
58
- it "does nothing" do
59
- expect(FileUtils).to_not receive(:chmod)
60
-
61
- subject.uids
25
+ describe "#exist?" do
26
+ context "when the mailbox exists" do
27
+ it "is true" do
28
+ expect(subject.exist?).to be true
29
+ end
62
30
  end
63
- end
64
31
 
65
- context "when it exists" do
66
- it "is not created" do
67
- expect(Imap::Backup::Utils).to_not receive(:make_folder).
68
- with(base_path, File.dirname(imap_folder), 0o700)
32
+ context "when the mailbox doesn't exist" do
33
+ let(:exists) { false }
69
34
 
70
- subject.uids
35
+ it "is false" do
36
+ expect(subject.exist?).to be false
37
+ end
71
38
  end
72
39
  end
73
- end
74
-
75
- describe "#apply_uid_validity" do
76
- context "when the existing uid validity is unset" do
77
- it "sets uid validity" do
78
- expect(store).to receive(:uid_validity=).with("aaa")
79
40
 
80
- subject.apply_uid_validity("aaa")
81
- end
41
+ describe "#length" do
42
+ let(:stat) { instance_double(File::Stat, size: 99) }
82
43
 
83
- it "does not rename the store" do
84
- expect(store).to_not receive(:rename)
44
+ before { allow(File).to receive(:stat) { stat } }
85
45
 
86
- subject.apply_uid_validity("aaa")
87
- end
88
-
89
- it "returns nil" do
90
- expect(subject.apply_uid_validity("aaa")).to be_nil
46
+ it "returns the length of the mailbox file" do
47
+ expect(subject.length).to eq(99)
91
48
  end
92
49
  end
93
50
 
94
- context "when the uid validity is unchanged" do
95
- let(:existing_uid_validity) { "aaa" }
96
-
97
- it "does not set uid validity" do
98
- expect(store).to_not receive(:uid_validity=)
99
-
100
- subject.apply_uid_validity("aaa")
101
- end
102
-
103
- it "does not rename the store" do
104
- expect(store).to_not receive(:rename)
105
-
106
- subject.apply_uid_validity("aaa")
107
- end
108
-
109
- it "returns nil" do
110
- expect(subject.apply_uid_validity("aaa")).to be_nil
51
+ describe "#pathname" do
52
+ it "is the folder_path plus .mbox" do
53
+ expect(subject.pathname).to eq("folder_path.mbox")
111
54
  end
112
55
  end
113
56
 
114
- context "when the uid validity is changed" do
115
- let(:existing_uid_validity) { "bbb" }
116
- let(:existing_store) do
117
- instance_double(Imap::Backup::Serializer::MboxStore)
118
- end
119
- let(:exists) { false }
120
-
121
- before do
122
- allow(Imap::Backup::Serializer::MboxStore).
123
- to receive(:new).with(anything, /bbb/) { existing_store }
124
- allow(existing_store).to receive(:exist?).and_return(exists, false)
125
- end
126
-
127
- it "sets uid validity" do
128
- expect(store).to receive(:uid_validity=).with("aaa")
57
+ describe "#rename" do
58
+ context "when the mailbox exists" do
59
+ let(:exists) { true }
129
60
 
130
- subject.apply_uid_validity("aaa")
131
- end
61
+ before do
62
+ allow(File).to receive(:rename)
132
63
 
133
- context "when adding the uid validity does not cause a name clash" do
134
- it "renames the store, adding the existing uid validity" do
135
- expect(store).to receive(:rename).with("folder.bbb")
64
+ subject.rename("new_name")
65
+ end
136
66
 
137
- subject.apply_uid_validity("aaa")
67
+ it "renames the mailbox" do
68
+ expect(File).to have_received(:rename)
138
69
  end
139
70
 
140
- it "returns the new name" do
141
- expect(subject.apply_uid_validity("aaa")).to eq("folder.bbb")
71
+ it "sets the folder_path" do
72
+ expect(subject.folder_path).to eq("new_name")
142
73
  end
143
74
  end
144
75
 
145
- context "when adding the uid validity causes a name clash" do
146
- let(:exists) { true }
147
-
148
- it "renames the store, adding the existing uid validity and a digit" do
149
- expect(store).to receive(:rename).with("folder.bbb.1")
76
+ context "when the mailbox doesn't exist" do
77
+ let(:exists) { false }
150
78
 
151
- subject.apply_uid_validity("aaa")
152
- end
79
+ it "sets the folder_path" do
80
+ subject.rename("new_name")
153
81
 
154
- it "returns the new name" do
155
- expect(subject.apply_uid_validity("aaa")).to eq("folder.bbb.1")
82
+ expect(subject.folder_path).to eq("new_name")
156
83
  end
157
84
  end
158
85
  end
159
- end
160
-
161
- describe "#force_uid_validity" do
162
- it "sets the uid_validity" do
163
- expect(store).to receive(:uid_validity=).with("66")
164
-
165
- subject.force_uid_validity("66")
166
- end
167
- end
168
-
169
- describe "#uids" do
170
- it "calls the store" do
171
- expect(store).to receive(:uids)
172
-
173
- subject.uids
174
- end
175
- end
176
-
177
- describe "#load" do
178
- before { allow(store).to receive(:load).with("66") { "xxx" } }
179
-
180
- it "returns the value loaded by the store" do
181
- expect(subject.load("66")).to eq("xxx")
182
- end
183
- end
184
-
185
- describe "#each_message" do
186
- it "calls the store" do
187
- expect(store).to receive(:each_message).with([1])
188
86
 
189
- subject.each_message([1])
190
- end
191
- end
192
-
193
- describe "#save" do
194
- it "calls the store" do
195
- expect(store).to receive(:add).with("foo", "bar")
196
-
197
- subject.save("foo", "bar")
198
- end
199
- end
200
-
201
- describe "#rename" do
202
- it "calls the store" do
203
- expect(store).to receive(:rename).with("foo")
204
-
205
- subject.rename("foo")
206
- end
207
-
208
- it "updates the folder name" do
209
- subject.rename("foo")
210
-
211
- expect(subject.folder).to eq("foo")
212
- end
213
- end
87
+ describe "#rewind" do
88
+ before do
89
+ allow(File).to receive(:open).
90
+ with(pathname, File::RDWR | File::CREAT, 0o644).
91
+ and_yield(file)
92
+ end
214
93
 
215
- describe "#update_uid" do
216
- it "calls the store" do
217
- expect(store).to receive(:update_uid).with("foo", "bar")
94
+ it "truncates the mailbox" do
95
+ subject.rewind(123)
218
96
 
219
- subject.update_uid("foo", "bar")
97
+ expect(file).to have_received(:truncate).with(123)
98
+ end
220
99
  end
221
100
  end
222
101
  end