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.
- checksums.yaml +4 -4
- data/.rspec +0 -1
- data/.rubocop.yml +5 -2
- data/.rubocop_todo.yml +24 -0
- data/.travis.yml +13 -1
- data/README.md +9 -21
- data/Rakefile +6 -2
- data/imap-backup.gemspec +5 -1
- data/lib/email/mboxrd/message.rb +13 -12
- data/lib/imap/backup/account/connection.rb +3 -2
- data/lib/imap/backup/account/folder.rb +2 -0
- data/lib/imap/backup/configuration/account.rb +20 -16
- data/lib/imap/backup/configuration/asker.rb +1 -1
- data/lib/imap/backup/serializer/mbox.rb +39 -25
- data/lib/imap/backup/serializer/mbox_enumerator.rb +31 -0
- data/lib/imap/backup/serializer/mbox_store.rb +6 -27
- data/lib/imap/backup/version.rb +1 -1
- data/spec/features/support/email_server.rb +3 -0
- data/spec/support/fixtures.rb +1 -1
- data/spec/unit/email/mboxrd/message_spec.rb +20 -3
- data/spec/unit/email/provider_spec.rb +1 -1
- data/spec/unit/imap/backup/account/connection_spec.rb +10 -9
- data/spec/unit/imap/backup/account/folder_spec.rb +24 -10
- data/spec/unit/imap/backup/configuration/account_spec.rb +47 -22
- data/spec/unit/imap/backup/configuration/asker_spec.rb +5 -9
- data/spec/unit/imap/backup/configuration/connection_tester_spec.rb +6 -6
- data/spec/unit/imap/backup/configuration/folder_chooser_spec.rb +6 -6
- data/spec/unit/imap/backup/configuration/list_spec.rb +24 -1
- data/spec/unit/imap/backup/configuration/setup_spec.rb +29 -9
- data/spec/unit/imap/backup/configuration/store_spec.rb +5 -5
- data/spec/unit/imap/backup/downloader_spec.rb +11 -13
- data/spec/unit/imap/backup/serializer/mbox_enumerator_spec.rb +40 -0
- data/spec/unit/imap/backup/serializer/mbox_spec.rb +63 -24
- data/spec/unit/imap/backup/serializer/mbox_store_spec.rb +162 -9
- data/spec/unit/imap/backup/utils_spec.rb +3 -3
- data/spec/unit/imap/backup_spec.rb +28 -0
- 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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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 "
|
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
|
-
|
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 "
|
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 "
|
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")
|
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 "
|
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 "
|
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 "
|
27
|
-
|
28
|
-
|
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
|
-
|
34
|
-
|
35
|
-
|
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
|
-
|
40
|
-
|
41
|
-
|
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
|
-
|
30
|
+
describe "folder path" do
|
30
31
|
before { subject.uids }
|
31
32
|
|
32
|
-
context "when
|
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
|
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
|
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
|
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
|
-
|
69
|
-
|
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
|