imap-backup 2.1.0 → 2.1.1

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 (37) hide show
  1. checksums.yaml +4 -4
  2. data/.rspec +0 -1
  3. data/.rubocop.yml +5 -2
  4. data/.rubocop_todo.yml +24 -0
  5. data/.travis.yml +13 -1
  6. data/README.md +9 -21
  7. data/Rakefile +6 -2
  8. data/imap-backup.gemspec +5 -1
  9. data/lib/email/mboxrd/message.rb +13 -12
  10. data/lib/imap/backup/account/connection.rb +3 -2
  11. data/lib/imap/backup/account/folder.rb +2 -0
  12. data/lib/imap/backup/configuration/account.rb +20 -16
  13. data/lib/imap/backup/configuration/asker.rb +1 -1
  14. data/lib/imap/backup/serializer/mbox.rb +39 -25
  15. data/lib/imap/backup/serializer/mbox_enumerator.rb +31 -0
  16. data/lib/imap/backup/serializer/mbox_store.rb +6 -27
  17. data/lib/imap/backup/version.rb +1 -1
  18. data/spec/features/support/email_server.rb +3 -0
  19. data/spec/support/fixtures.rb +1 -1
  20. data/spec/unit/email/mboxrd/message_spec.rb +20 -3
  21. data/spec/unit/email/provider_spec.rb +1 -1
  22. data/spec/unit/imap/backup/account/connection_spec.rb +10 -9
  23. data/spec/unit/imap/backup/account/folder_spec.rb +24 -10
  24. data/spec/unit/imap/backup/configuration/account_spec.rb +47 -22
  25. data/spec/unit/imap/backup/configuration/asker_spec.rb +5 -9
  26. data/spec/unit/imap/backup/configuration/connection_tester_spec.rb +6 -6
  27. data/spec/unit/imap/backup/configuration/folder_chooser_spec.rb +6 -6
  28. data/spec/unit/imap/backup/configuration/list_spec.rb +24 -1
  29. data/spec/unit/imap/backup/configuration/setup_spec.rb +29 -9
  30. data/spec/unit/imap/backup/configuration/store_spec.rb +5 -5
  31. data/spec/unit/imap/backup/downloader_spec.rb +11 -13
  32. data/spec/unit/imap/backup/serializer/mbox_enumerator_spec.rb +40 -0
  33. data/spec/unit/imap/backup/serializer/mbox_spec.rb +63 -24
  34. data/spec/unit/imap/backup/serializer/mbox_store_spec.rb +162 -9
  35. data/spec/unit/imap/backup/utils_spec.rb +3 -3
  36. data/spec/unit/imap/backup_spec.rb +28 -0
  37. metadata +8 -2
@@ -1,5 +1,3 @@
1
- # rubocop:disable Metrics/ModuleLength
2
-
3
1
  module Imap::Backup
4
2
  describe Configuration::Asker do
5
3
  subject { described_class.new(highline) }
@@ -43,7 +41,7 @@ module Imap::Backup
43
41
  end
44
42
  end
45
43
 
46
- context "#initialize" do
44
+ describe "#initialize" do
47
45
  it "requires 1 parameter" do
48
46
  expect do
49
47
  described_class.new
@@ -55,7 +53,7 @@ module Imap::Backup
55
53
  end
56
54
  end
57
55
 
58
- context "#email" do
56
+ describe "#email" do
59
57
  let(:email) { "email@example.com" }
60
58
  let(:answer) { email }
61
59
  let(:result) { subject.email }
@@ -71,7 +69,7 @@ module Imap::Backup
71
69
  end
72
70
  end
73
71
 
74
- context "#password" do
72
+ describe "#password" do
75
73
  let(:password1) { "password" }
76
74
  let(:password2) { "password" }
77
75
  let(:answers) { [true, false] }
@@ -101,7 +99,7 @@ module Imap::Backup
101
99
  expect(result).to eq(password1)
102
100
  end
103
101
 
104
- context "different answers" do
102
+ context "with different answers" do
105
103
  let(:password2) { "secret" }
106
104
 
107
105
  it "asks to continue" do
@@ -111,7 +109,7 @@ module Imap::Backup
111
109
  end
112
110
  end
113
111
 
114
- context "#backup_path" do
112
+ describe "#backup_path" do
115
113
  let(:path) { "/path" }
116
114
  let(:answer) { path }
117
115
  let(:result) { subject.backup_path("", //) }
@@ -134,5 +132,3 @@ module Imap::Backup
134
132
  end
135
133
  end
136
134
  end
137
-
138
- # rubocop:enable Metrics/ModuleLength
@@ -1,5 +1,5 @@
1
1
  describe Imap::Backup::Configuration::ConnectionTester do
2
- context ".test" do
2
+ describe ".test" do
3
3
  let(:connection) do
4
4
  instance_double(Imap::Backup::Account::Connection, imap: nil)
5
5
  end
@@ -9,7 +9,7 @@ describe Imap::Backup::Configuration::ConnectionTester do
9
9
  allow(Imap::Backup::Account::Connection).to receive(:new) { connection }
10
10
  end
11
11
 
12
- context "call" do
12
+ describe "call" do
13
13
  before { result }
14
14
 
15
15
  it "tries to connect" do
@@ -17,7 +17,7 @@ describe Imap::Backup::Configuration::ConnectionTester do
17
17
  end
18
18
  end
19
19
 
20
- context "success" do
20
+ describe "success" do
21
21
  before { result }
22
22
 
23
23
  it "returns success" do
@@ -25,13 +25,13 @@ describe Imap::Backup::Configuration::ConnectionTester do
25
25
  end
26
26
  end
27
27
 
28
- context "failure" do
28
+ describe "failure" do
29
29
  before do
30
30
  allow(connection).to receive(:imap).and_raise(error)
31
31
  result
32
32
  end
33
33
 
34
- context "no connection" do
34
+ context "with no connection" do
35
35
  let(:error) do
36
36
  data = OpenStruct.new(text: "bar")
37
37
  response = OpenStruct.new(data: data)
@@ -43,7 +43,7 @@ describe Imap::Backup::Configuration::ConnectionTester do
43
43
  end
44
44
  end
45
45
 
46
- context "other" do
46
+ context "when caused by other errors" do
47
47
  let(:error) { "Error" }
48
48
 
49
49
  it "returns error" do
@@ -1,7 +1,7 @@
1
1
  describe Imap::Backup::Configuration::FolderChooser do
2
2
  include HighLineTestHelpers
3
3
 
4
- context "#run" do
4
+ describe "#run" do
5
5
  subject { described_class.new(account) }
6
6
 
7
7
  let(:connection) do
@@ -21,7 +21,7 @@ describe Imap::Backup::Configuration::FolderChooser do
21
21
  allow(Imap::Backup.logger).to receive(:warn)
22
22
  end
23
23
 
24
- context "display" do
24
+ describe "display" do
25
25
  before { subject.run }
26
26
 
27
27
  it "clears the screen" do
@@ -33,7 +33,7 @@ describe Imap::Backup::Configuration::FolderChooser do
33
33
  end
34
34
  end
35
35
 
36
- context "folder listing" do
36
+ describe "folder listing" do
37
37
  let(:account) { {folders: [{name: "my_folder"}]} }
38
38
  let(:remote_folders) do
39
39
  # this one is already backed up:
@@ -46,7 +46,7 @@ describe Imap::Backup::Configuration::FolderChooser do
46
46
  [folder1, folder2]
47
47
  end
48
48
 
49
- context "display" do
49
+ describe "display" do
50
50
  before { subject.run }
51
51
 
52
52
  it "shows folders which are being backed up" do
@@ -58,7 +58,7 @@ describe Imap::Backup::Configuration::FolderChooser do
58
58
  end
59
59
  end
60
60
 
61
- context "adding folders" do
61
+ context "when adding folders" do
62
62
  before do
63
63
  allow(input).to receive(:gets).and_return("2\n", "q\n")
64
64
 
@@ -72,7 +72,7 @@ describe Imap::Backup::Configuration::FolderChooser do
72
72
  include_examples "it flags the account as modified"
73
73
  end
74
74
 
75
- context "removing folders" do
75
+ context "when removing folders" do
76
76
  before do
77
77
  allow(input).to receive(:gets).and_return("1\n", "q\n")
78
78
 
@@ -28,7 +28,30 @@ describe Imap::Backup::Configuration::List do
28
28
  to receive(:new).with(accounts[1]) { connection2 }
29
29
  end
30
30
 
31
- context "#each_connection" do
31
+ describe "#setup_logging" do
32
+ let(:config_exists) { true }
33
+
34
+ before do
35
+ allow(Imap::Backup::Configuration::Store).
36
+ to receive(:exist?) { config_exists }
37
+ allow(Imap::Backup).to receive(:setup_logging)
38
+ subject.setup_logging
39
+ end
40
+
41
+ it "sets global logging level" do
42
+ expect(Imap::Backup).to have_received(:setup_logging).with(store)
43
+ end
44
+
45
+ context "without a config" do
46
+ let(:config_exists) { false }
47
+
48
+ it "does nothing" do
49
+ expect(Imap::Backup).to_not have_received(:setup_logging).with(store)
50
+ end
51
+ end
52
+ end
53
+
54
+ describe "#each_connection" do
32
55
  specify "calls the block with each account's connection" do
33
56
  connections = []
34
57
 
@@ -3,7 +3,7 @@
3
3
  describe Imap::Backup::Configuration::Setup do
4
4
  include HighLineTestHelpers
5
5
 
6
- context "#initialize" do
6
+ describe "#initialize" do
7
7
  context "without a config file" do
8
8
  it "works" do
9
9
  described_class.new
@@ -11,7 +11,7 @@ describe Imap::Backup::Configuration::Setup do
11
11
  end
12
12
  end
13
13
 
14
- context "#run" do
14
+ describe "#run" do
15
15
  subject { described_class.new }
16
16
 
17
17
  let(:normal) { {username: "account@example.com"} }
@@ -41,7 +41,7 @@ describe Imap::Backup::Configuration::Setup do
41
41
  allow(Kernel).to receive(:system)
42
42
  end
43
43
 
44
- context "main menu" do
44
+ describe "main menu" do
45
45
  before { subject.run }
46
46
 
47
47
  %w(add\ account save\ and\ exit exit\ without\ saving).each do |choice|
@@ -63,33 +63,53 @@ describe Imap::Backup::Configuration::Setup do
63
63
  expect(Imap::Backup).to have_received(:setup_logging)
64
64
  end
65
65
 
66
- context "listing" do
66
+ describe "listing" do
67
67
  let(:accounts) { [normal, modified, deleted] }
68
68
  let(:modified) { {username: "modified@example.com", modified: true} }
69
69
  let(:deleted) { {username: "deleted@example.com", delete: true} }
70
70
 
71
71
  before { subject.run }
72
72
 
73
- context "normal accounts" do
73
+ describe "normal accounts" do
74
74
  it "are listed" do
75
75
  expect(output.string).to match(/account@example.com/)
76
76
  end
77
77
  end
78
78
 
79
- context "modified accounts" do
79
+ describe "modified accounts" do
80
80
  it "are flagged" do
81
81
  expect(output.string).to match(/modified@example.com \*/)
82
82
  end
83
83
  end
84
84
 
85
- context "deleted accounts" do
85
+ describe "deleted accounts" do
86
86
  it "are hidden" do
87
87
  expect(output.string).to_not match(/delete@example.com/)
88
88
  end
89
89
  end
90
90
  end
91
91
 
92
- context "adding accounts" do
92
+ context "when editing accounts" do
93
+ let(:account) do
94
+ instance_double(Imap::Backup::Configuration::Account, run: nil)
95
+ end
96
+
97
+ before do
98
+ allow(input).to receive(:gets).and_return("1\n", "exit\n")
99
+ allow(Imap::Backup::Configuration::Asker).to receive(:email).
100
+ with(no_args).and_return("new@example.com")
101
+ allow(Imap::Backup::Configuration::Account).to receive(:new).
102
+ with(store, normal, anything).and_return(account)
103
+
104
+ subject.run
105
+ end
106
+
107
+ it "edits the account" do
108
+ expect(account).to have_received(:run)
109
+ end
110
+ end
111
+
112
+ context "when adding accounts" do
93
113
  let(:blank_account) do
94
114
  {
95
115
  username: "new@example.com",
@@ -121,7 +141,7 @@ describe Imap::Backup::Configuration::Setup do
121
141
  end
122
142
  end
123
143
 
124
- context "logging" do
144
+ describe "logging" do
125
145
  context "when debug logging is disabled" do
126
146
  before do
127
147
  allow(input).to receive(:gets).and_return("start\n", "exit\n")
@@ -47,7 +47,7 @@ describe Imap::Backup::Configuration::Store do
47
47
  end
48
48
 
49
49
  describe "#modified?" do
50
- context "'with accounts flagged 'modified'" do
50
+ context "with accounts flagged 'modified'" do
51
51
  let(:accounts) { [{name: "foo", modified: true}] }
52
52
 
53
53
  it "is true" do
@@ -55,7 +55,7 @@ describe Imap::Backup::Configuration::Store do
55
55
  end
56
56
  end
57
57
 
58
- context "'with accounts flagged 'delete'" do
58
+ context "with accounts flagged 'delete'" do
59
59
  let(:accounts) { [{name: "foo", delete: true}] }
60
60
 
61
61
  it "is true" do
@@ -139,7 +139,7 @@ describe Imap::Backup::Configuration::Store do
139
139
  before do
140
140
  allow(FileUtils).to receive(:mkdir)
141
141
  allow(FileUtils).to receive(:chmod)
142
- allow(File).to receive(:open).with(file_path, "w") { |&b| b.call file }
142
+ allow(File).to receive(:open).with(file_path, "w").and_yield(file)
143
143
  allow(JSON).to receive(:pretty_generate).and_return("JSON output")
144
144
  end
145
145
 
@@ -194,7 +194,7 @@ describe Imap::Backup::Configuration::Store do
194
194
  end
195
195
  end
196
196
 
197
- context "if the configuration file is missing" do
197
+ context "when the configuration file is missing" do
198
198
  let(:file_exists) { false }
199
199
 
200
200
  it "doesn't fail" do
@@ -204,7 +204,7 @@ describe Imap::Backup::Configuration::Store do
204
204
  end
205
205
  end
206
206
 
207
- context "if the config file permissions are too lax" do
207
+ context "when the config file permissions are too lax" do
208
208
  let(:file_exists) { true }
209
209
 
210
210
  before do
@@ -23,23 +23,21 @@ describe Imap::Backup::Downloader do
23
23
  subject.run
24
24
  end
25
25
 
26
- context "#run" do
27
- context "fetched messages" do
28
- it "are saved" do
29
- expect(serializer).to have_received(:save).with("111", message)
30
- end
26
+ context "with fetched messages" do
27
+ it "are saved" do
28
+ expect(serializer).to have_received(:save).with("111", message)
31
29
  end
30
+ end
32
31
 
33
- context "messages which are already present" do
34
- specify "are skipped" do
35
- expect(serializer).to_not have_received(:save).with("222", anything)
36
- end
32
+ context "with messages which are already present" do
33
+ specify "are skipped" do
34
+ expect(serializer).to_not have_received(:save).with("222", anything)
37
35
  end
36
+ end
38
37
 
39
- context "failed fetches" do
40
- specify "are skipped" do
41
- expect(serializer).to_not have_received(:save).with("333", anything)
42
- end
38
+ context "with failed fetches" do
39
+ specify "are skipped" do
40
+ expect(serializer).to_not have_received(:save).with("333", anything)
43
41
  end
44
42
  end
45
43
  end
@@ -0,0 +1,40 @@
1
+ require "imap/backup/serializer/mbox_enumerator"
2
+
3
+ describe Imap::Backup::Serializer::MboxEnumerator do
4
+ subject { described_class.new(mbox_pathname) }
5
+
6
+ let(:mbox_pathname) { "/mbox/pathname" }
7
+ let(:mbox_file) { instance_double(File) }
8
+ let(:lines) { message1 + message2 + [nil] }
9
+ let(:message1) do
10
+ [
11
+ "From Frida\r\n",
12
+ "Hello\r\n"
13
+ ]
14
+ end
15
+ let(:message2) do
16
+ [
17
+ "From John\r\n",
18
+ "Hi\r\n"
19
+ ]
20
+ end
21
+
22
+ before do
23
+ allow(File).to receive(:open).and_call_original
24
+ allow(File).to receive(:open).with(mbox_pathname).and_yield(mbox_file)
25
+ allow(mbox_file).to receive(:gets).and_return(*lines)
26
+ end
27
+
28
+ describe "#each" do
29
+ it "yields messages" do
30
+ expect { |b| subject.each(&b) }.
31
+ to yield_successive_args(message1.join, message2.join)
32
+ end
33
+
34
+ context "without a block" do
35
+ it "returns an Enumerator" do
36
+ expect(subject.each).to be_a(Enumerator)
37
+ end
38
+ end
39
+ end
40
+ end
@@ -9,7 +9,8 @@ describe Imap::Backup::Serializer::Mbox do
9
9
  rename: nil,
10
10
  uids: nil,
11
11
  uid_validity: existing_uid_validity,
12
- "uid_validity=": nil
12
+ "uid_validity=": nil,
13
+ update_uid: nil
13
14
  )
14
15
  end
15
16
  let(:imap_folder) { "folder" }
@@ -26,10 +27,10 @@ describe Imap::Backup::Serializer::Mbox do
26
27
  allow(Imap::Backup::Serializer::MboxStore).to receive(:new) { store }
27
28
  end
28
29
 
29
- context "containing directory" do
30
+ describe "folder path" do
30
31
  before { subject.uids }
31
32
 
32
- context "when the IMAP folder has multiple elements" do
33
+ context "when it has multiple elements" do
33
34
  let(:imap_folder) { "folder/path" }
34
35
 
35
36
  context "when the containing directory is missing" do
@@ -42,7 +43,7 @@ describe Imap::Backup::Serializer::Mbox do
42
43
  end
43
44
  end
44
45
 
45
- context "when the containing directory permissons are incorrect" do
46
+ context "when permissions are incorrect" do
46
47
  let(:permissions) { 0o777 }
47
48
 
48
49
  it "corrects them" do
@@ -51,13 +52,13 @@ describe Imap::Backup::Serializer::Mbox do
51
52
  end
52
53
  end
53
54
 
54
- context "when the containing directory permissons are correct" do
55
+ context "when permissons are correct" do
55
56
  it "does nothing" do
56
57
  expect(FileUtils).to_not have_received(:chmod)
57
58
  end
58
59
  end
59
60
 
60
- context "when the containing directory exists" do
61
+ context "when it exists" do
61
62
  it "is not created" do
62
63
  expect(Imap::Backup::Utils).to_not have_received(:make_folder).
63
64
  with(base_path, File.dirname(imap_folder), 0o700)
@@ -65,24 +66,8 @@ describe Imap::Backup::Serializer::Mbox do
65
66
  end
66
67
  end
67
68
 
68
- context "#uids" do
69
- it "calls the store" do
70
- subject.uids
71
-
72
- expect(store).to have_received(:uids)
73
- end
74
- end
75
-
76
- context "#save" do
77
- it "calls the store" do
78
- subject.save("foo", "bar")
79
-
80
- expect(store).to have_received(:add)
81
- end
82
- end
83
-
84
- context "#set_uid_validity" do
85
- let(:result) { subject.set_uid_validity("aaa") }
69
+ describe "#apply_uid_validity" do
70
+ let(:result) { subject.apply_uid_validity("aaa") }
86
71
 
87
72
  context "when the existing uid validity is unset" do
88
73
  let!(:result) { super() }
@@ -158,4 +143,58 @@ describe Imap::Backup::Serializer::Mbox do
158
143
  end
159
144
  end
160
145
  end
146
+
147
+ describe "#force_uid_validity" do
148
+ before { subject.force_uid_validity("66") }
149
+
150
+ it "sets the uid_validity" do
151
+ expect(store).to have_received(:uid_validity=).with("66")
152
+ end
153
+ end
154
+
155
+ describe "#uids" do
156
+ it "calls the store" do
157
+ subject.uids
158
+
159
+ expect(store).to have_received(:uids)
160
+ end
161
+ end
162
+
163
+ describe "#load" do
164
+ let(:result) { subject.load("66") }
165
+
166
+ before { allow(store).to receive(:load).with("66") { "xxx" } }
167
+
168
+ it "returns the value loaded by the store" do
169
+ expect(result).to eq("xxx")
170
+ end
171
+ end
172
+
173
+ describe "#save" do
174
+ before { subject.save("foo", "bar") }
175
+
176
+ it "calls the store" do
177
+ expect(store).to have_received(:add).with("foo", "bar")
178
+ end
179
+ end
180
+
181
+ describe "#rename" do
182
+ before { subject.rename("foo") }
183
+
184
+ it "calls the store" do
185
+ expect(store).to have_received(:rename).with("foo")
186
+ end
187
+
188
+ it "updates the folder name" do
189
+ expect(subject.folder).to eq("foo")
190
+ end
191
+ end
192
+
193
+ describe "#update_uid" do
194
+ before { subject.update_uid("foo", "bar") }
195
+
196
+ it "calls the store" do
197
+ expect(store).to have_received(:update_uid).with("foo", "bar")
198
+ end
199
+ end
161
200
  end